From 8664bda38e7c762469eec136febdff798a201bd3 Mon Sep 17 00:00:00 2001 From: chrissolutions Date: Fri, 17 Apr 2020 17:49:33 +1200 Subject: [PATCH 1/6] Added PropertyMap support to RepoDb. --- .../RepoDb.IntegrationTests/Setup/Database.cs | 11 +- .../TypeConversionsTest.cs | 28 +- RepoDb.Core/RepoDb/Entity/EntityBase.cs | 35 +++ .../RepoDb/Entity/EntityPropertyConverter.cs | 30 ++ .../RepoDb/Entity/EntityPropertyMap.cs | 94 +++++++ .../RepoDb/Extensions/DictionaryExtension.cs | 49 ++++ .../RepoDb/Extensions/EnumerableExtension.cs | 28 ++ RepoDb.Core/RepoDb/Interfaces/IConverter.cs | 30 ++ RepoDb.Core/RepoDb/Interfaces/IPropertyMap.cs | 27 ++ .../RepoDb/Reflection/FunctionFactory.cs | 259 +++++++++++++++++- RepoDb.Core/RepoDb/RepoDb.csproj | 1 + 11 files changed, 582 insertions(+), 10 deletions(-) create mode 100644 RepoDb.Core/RepoDb/Entity/EntityBase.cs create mode 100644 RepoDb.Core/RepoDb/Entity/EntityPropertyConverter.cs create mode 100644 RepoDb.Core/RepoDb/Entity/EntityPropertyMap.cs create mode 100644 RepoDb.Core/RepoDb/Extensions/DictionaryExtension.cs create mode 100644 RepoDb.Core/RepoDb/Interfaces/IConverter.cs create mode 100644 RepoDb.Core/RepoDb/Interfaces/IPropertyMap.cs diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs index 0027aec1d..9d151d704 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs @@ -18,11 +18,18 @@ public static void Initialize() var connectionStringForMaster = Environment.GetEnvironmentVariable("REPODB_CONSTR_MASTER", EnvironmentVariableTarget.Process); var connectionString = Environment.GetEnvironmentVariable("REPODB_CONSTR", EnvironmentVariableTarget.Process); + //// Master connection + //ConnectionStringForMaster = (connectionStringForMaster ?? @"Server=(local);Database=master;Integrated Security=False;User Id=michael;Password=Password123;"); + + //// RepoDb connection + //ConnectionStringForRepoDb = (connectionString ?? @"Server=(local);Database=RepoDb;Integrated Security=False;User Id=michael;Password=Password123;"); + // Master connection - ConnectionStringForMaster = (connectionStringForMaster ?? @"Server=(local);Database=master;Integrated Security=False;User Id=michael;Password=Password123;"); + ConnectionStringForMaster = (connectionStringForMaster ?? @"Data Source=CHRISWA;Initial Catalog=master;Trusted_Connection=True;"); // RepoDb connection - ConnectionStringForRepoDb = (connectionString ?? @"Server=(local);Database=RepoDb;Integrated Security=False;User Id=michael;Password=Password123;"); + ConnectionStringForRepoDb = (connectionString ?? @"Data Source=CHRISWA;Initial Catalog=RepoDb;Trusted_Connection=True;"); + // Set the proper values for type mapper TypeMapper.Add(typeof(DateTime), System.Data.DbType.DateTime2, true); diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs index 789fc55f1..623e0f3cc 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs @@ -5,6 +5,7 @@ using System; using Microsoft.Data.SqlClient; using System.Linq; +using RepoDb.Entity; namespace RepoDb.IntegrationTests { @@ -391,15 +392,20 @@ public void TestSqlConnectionCrudConvertionFromStringToSmallMoney() #region StringToDateClass [Map("CompleteTable")] - private class StringToDateClass + private class StringToDateClass : EntityBase { [Primary] public Guid SessionId { get; set; } public string ColumnDate { get; set; } + + public StringToDateClass() + { + Map(p => p.ColumnDate).Convert(o => o.ToString("M'/'d'/'yyyy h:mm:ss tt")); + } } [TestMethod] - public void TestSqlConnectionCrudConvertionFromStringToDate() + public void TestSqlConnectionCrudConversionFromStringToDate() { // Setup var entity = new StringToDateClass @@ -426,15 +432,20 @@ public void TestSqlConnectionCrudConvertionFromStringToDate() #region StringToDateTimeClass [Map("CompleteTable")] - private class StringToDateTimeClass + private class StringToDateTimeClass : EntityBase { [Primary] public Guid SessionId { get; set; } public string ColumnDateTime { get; set; } + + public StringToDateTimeClass() + { + Map(p => p.ColumnDateTime).Convert(o => o.ToString("M'/'d'/'yyyy h:mm:ss tt")); + } } [TestMethod] - public void TestSqlConnectionCrudConvertionFromStringToDateTime() + public void TestSqlConnectionCrudConversionFromStringToDateTime() { // Setup var entity = new StringToDateTimeClass @@ -461,15 +472,20 @@ public void TestSqlConnectionCrudConvertionFromStringToDateTime() #region StringToDateTime2Class [Map("CompleteTable")] - private class StringToDateTime2Class + private class StringToDateTime2Class : EntityBase { [Primary] public Guid SessionId { get; set; } public string ColumnDateTime2 { get; set; } + + public StringToDateTime2Class() + { + Map(p => p.ColumnDateTime2).Convert(o => o.ToString("M'/'d'/'yyyy h:mm:ss tt")); + } } [TestMethod] - public void TestSqlConnectionCrudConvertionFromStringToDateTime2() + public void TestSqlConnectionCrudConversionFromStringToDateTime2() { // Setup var entity = new StringToDateTime2Class diff --git a/RepoDb.Core/RepoDb/Entity/EntityBase.cs b/RepoDb.Core/RepoDb/Entity/EntityBase.cs new file mode 100644 index 000000000..54dd4e242 --- /dev/null +++ b/RepoDb.Core/RepoDb/Entity/EntityBase.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq.Expressions; +using RepoDb.Extensions; +using RepoDb.Interfaces; + +namespace RepoDb.Entity +{ + /// + /// + /// + /// + public abstract class EntityBase where TEntity : class + { + /// + /// + /// + public static readonly IPropertyMap PropertyMap = new EntityPropertyMap(); + + static EntityBase() + { + typeof(TEntity).GetProperties().ForEach(x => PropertyMap.Map(x)); + } + + /// + /// Map property by expression. + /// + /// type of the property + /// mapping expression + /// mapper + public IConverter Map(Expression> expression) + { + return PropertyMap.Map(expression); + } + } +} diff --git a/RepoDb.Core/RepoDb/Entity/EntityPropertyConverter.cs b/RepoDb.Core/RepoDb/Entity/EntityPropertyConverter.cs new file mode 100644 index 000000000..61e7c1a4f --- /dev/null +++ b/RepoDb.Core/RepoDb/Entity/EntityPropertyConverter.cs @@ -0,0 +1,30 @@ +using System; +using RepoDb.Interfaces; + +namespace RepoDb.Entity +{ + internal class EntityPropertyConverter : IConverter + { + /// + /// + /// + internal object ToProperty { get; private set; } + + /// + /// + /// + internal object ToObject { get; private set; } + + public IConverter Convert(Func func) + { + ToObject = func; + return this; + } + + public IConverter Convert(Func func) + { + ToProperty = func; + return this; + } + } +} diff --git a/RepoDb.Core/RepoDb/Entity/EntityPropertyMap.cs b/RepoDb.Core/RepoDb/Entity/EntityPropertyMap.cs new file mode 100644 index 000000000..230f6c18c --- /dev/null +++ b/RepoDb.Core/RepoDb/Entity/EntityPropertyMap.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using RepoDb.Extensions; +using RepoDb.Interfaces; + +namespace RepoDb.Entity +{ + internal class EntityPropertyMap : IPropertyMap where TEntity : class + { + private readonly IDictionary m_propertyMap = new ConcurrentDictionary(); + + /// + /// Find the sql property from the property info map. + /// + /// property info + /// + public IConverter Find(PropertyInfo prop) + { + return m_propertyMap.TryGetValue(prop.Name, out var value) ? value : default; + } + + /// + /// Map property info. + /// + /// + /// + public IConverter Map(PropertyInfo propertyInfo) + { + var mapper = m_propertyMap.GetOrAdd(propertyInfo.Name, new EntityPropertyConverter()); + return mapper; + } + + /// + /// Map property by expression. + /// + /// type of the property + /// mapping expression + /// mapper + public IConverter Map(Expression> expression) + { + var info = (PropertyInfo) GetMemberInfo(expression); + return Map(info); + } + + /// + /// Returns the for the specified lambda expression. + /// + /// A lambda expression containing a MemberExpression. + /// A MemberInfo object for the member in the specified lambda expression. + private static MemberInfo GetMemberInfo(LambdaExpression lambda) + { + Expression expr = lambda; + while (true) + { + switch (expr.NodeType) + { + case ExpressionType.Lambda: + expr = ((LambdaExpression) expr).Body; + break; + + case ExpressionType.Convert: + expr = ((UnaryExpression) expr).Operand; + break; + + case ExpressionType.MemberAccess: + var memberExpression = (MemberExpression) expr; + var baseMember = memberExpression.Member; + + while (memberExpression != null) + { + var type = memberExpression.Type; + if (type.GetMembers().Any(member => member.Name == baseMember.Name)) + { + return type.GetMember(baseMember.Name).First(); + } + + memberExpression = memberExpression.Expression as MemberExpression; + } + + // Make sure we get the property from the derived type. + var paramType = lambda.Parameters[0].Type; + return paramType.GetMember(baseMember.Name).First(); + + default: + return null; + } + } + } + } +} \ No newline at end of file diff --git a/RepoDb.Core/RepoDb/Extensions/DictionaryExtension.cs b/RepoDb.Core/RepoDb/Extensions/DictionaryExtension.cs new file mode 100644 index 000000000..d745a6a2a --- /dev/null +++ b/RepoDb.Core/RepoDb/Extensions/DictionaryExtension.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; + +namespace RepoDb.Extensions +{ + /// + /// An extension class for . + /// + public static class DictionaryExtension + { + /// + /// Retrieve or add a value to dictionary + /// + /// key type + /// value type + /// dictionary + /// key value + /// value + /// value + public static TV GetOrAdd(this IDictionary dictionary, TK key, TV value) + { + if (!dictionary.ContainsKey(key)) + { + dictionary.Add(key, value); + } + + return dictionary[key]; + } + + /// + /// Retrieve or add a value to dictionary via add function. + /// + /// key type + /// value type + /// dictionary + /// key value + /// add function returning the value + /// value + public static TV GetOrAdd(this IDictionary dictionary, TK key, Func addFunc) + { + if (!dictionary.ContainsKey(key)) + { + dictionary.Add(key, addFunc()); + } + + return dictionary[key]; + } + } +} diff --git a/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs b/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs index 20c9a9a53..13db03d67 100644 --- a/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs +++ b/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs @@ -81,5 +81,33 @@ public static T[] AsArray(this IEnumerable value) { return value is T[]? (T[])value : value.ToArray(); } + + /// + /// For each extension. + /// + /// item type + /// collection + /// iterative action + public static void ForEach(this IEnumerable collection, Action eachAction) + { + var index = 0; + foreach (var item in collection) + { + eachAction?.Invoke(item, index++); + } + } + + /// + /// For each extension. + /// + /// item type + /// result type + /// collection + /// select function + /// returns list of results + public static IEnumerable ForEach(this IEnumerable collection, Func eachFunc) + { + return collection.Select(eachFunc.Invoke).ToList(); + } } } diff --git a/RepoDb.Core/RepoDb/Interfaces/IConverter.cs b/RepoDb.Core/RepoDb/Interfaces/IConverter.cs new file mode 100644 index 000000000..56af636c1 --- /dev/null +++ b/RepoDb.Core/RepoDb/Interfaces/IConverter.cs @@ -0,0 +1,30 @@ +using System; + +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedMemberInSuper.Global + +namespace RepoDb.Interfaces +{ + /// + /// An interface used to convert property values. + /// + public interface IConverter + { + /// + /// Set from property type to target converter. + /// + /// from property type + /// from converter + /// sql property + IConverter Convert(Func func); + + /// + /// Set to property type from source converter. + /// + /// from property type + /// to property type + /// to converter + /// sql property + IConverter Convert(Func func); + } +} \ No newline at end of file diff --git a/RepoDb.Core/RepoDb/Interfaces/IPropertyMap.cs b/RepoDb.Core/RepoDb/Interfaces/IPropertyMap.cs new file mode 100644 index 000000000..3158bf66e --- /dev/null +++ b/RepoDb.Core/RepoDb/Interfaces/IPropertyMap.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace RepoDb.Interfaces +{ + /// + /// An interface used to map a property converter. + /// + public interface IPropertyMap where TEntity : class + { + /// + /// Map property info. + /// + /// + /// + IConverter Map(PropertyInfo propertyInfo); + + /// + /// Map entity expression. + /// + /// property type + /// sql property + /// + IConverter Map(Expression> expression); + } +} diff --git a/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs index fb174a634..7d734d926 100644 --- a/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs +++ b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs @@ -13,6 +13,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using RepoDb.Entity; namespace RepoDb.Reflection { @@ -112,7 +113,7 @@ public static Func GetDataReaderToDataEntityConverterFunc // Matching the fields var readerFields = Enumerable.Range(0, reader.FieldCount) - .Select((index) => reader.GetName(index)) + .Select(reader.GetName) .Select((name, ordinal) => new DataReaderField { Name = name, @@ -122,7 +123,9 @@ public static Func GetDataReaderToDataEntityConverterFunc }); // Get the member assignments - var memberAssignments = GetMemberAssignmentsForDataEntity(newEntityExpression, readerParameterExpression, readerFields, connection); + var memberAssignments = typeof(TEntity).IsSubclassOf(typeof(EntityBase)) + ? GetMemberAssignmentsForDataEntity2(newEntityExpression, readerParameterExpression, readerFields, connection) + : GetMemberAssignmentsForDataEntity(newEntityExpression, readerParameterExpression, readerFields, connection); // Throw an error if there are no matching atleast one if (memberAssignments.Any() != true) @@ -532,6 +535,258 @@ private static IEnumerable GetMemberAssignmentsForDataEntity + /// Returns the list of the bindings for the entity. + /// + /// The target entity type. + /// The new entity expression. + /// The data reader parameter. + /// The list of fields to be bound from the data reader. + /// The used object. + /// The enumerable list of member assignment and bindings. + private static IEnumerable GetMemberAssignmentsForDataEntity2(Expression newEntityExpression, + ParameterExpression readerParameterExpression, + IEnumerable readerFields, + IDbConnection connection) + where TEntity : class + { + // Initialize variables + var memberAssignments = new List(); + var typeOfDbDataReader = typeof(DbDataReader); + var typeOfDateTime = typeof(DateTime); + var typeOfTimeSpan = typeof(TimeSpan); + var typeOfSingle = typeof(Single); + var properties = PropertyCache.Get().Where(property => property.PropertyInfo.CanWrite); + var dataReaderFields = readerFields as DataReaderField[] ?? readerFields.ToArray(); + var fieldNames = dataReaderFields.Select(f => f.Name.ToLower()).AsList(); + var dbSetting = connection?.GetDbSetting(); + + // Filter the properties by reader fields + properties = properties.Where(property => + fieldNames.FirstOrDefault(field => + string.Equals(field.AsUnquoted(true, dbSetting), property.GetMappedName().AsUnquoted(true, dbSetting), StringComparison.OrdinalIgnoreCase)) != null); + + // Iterate each properties + foreach (var classProperty in properties) + { + // Gets the mapped name and the ordinal + var mappedName = classProperty.GetMappedName().AsUnquoted(true, dbSetting); + var ordinal = fieldNames.IndexOf(mappedName.ToLower()); + + // Process only if there is a correct ordinal + if (ordinal >= 0) + { + // Variables needed for the iteration + var readerField = dataReaderFields.First(f => string.Equals(f.Name.AsUnquoted(true, dbSetting), mappedName.AsUnquoted(true, dbSetting), StringComparison.OrdinalIgnoreCase)); + var propertyType = classProperty.PropertyInfo.PropertyType; + var underlyingType = Nullable.GetUnderlyingType(propertyType); + var propertyMap = (EntityPropertyMap)EntityBase.PropertyMap; + var converterMethod = (Delegate)((EntityPropertyConverter)propertyMap?.Find(classProperty.PropertyInfo))?.ToProperty; + var parameters = converterMethod?.Method.GetParameters(); + var targetType = parameters?[0].ParameterType ?? underlyingType ?? propertyType; + + var convertType = readerField.Type; + var isConversionNeeded = readerField.Type != targetType; + var isNullable = readerField.DbField == null || readerField.DbField?.IsNullable == true; + + // Get the correct method info, if the reader.Get is not found, then use the default GetValue() method + var readerGetValueMethod = (MethodInfo)null; + + // Ignore for the TimeSpan + if (targetType != typeOfTimeSpan) + { + readerGetValueMethod = typeOfDbDataReader.GetMethod(string.Concat("Get", readerField.Type?.Name)); + } + + // If null, use the object + if (readerGetValueMethod == null) + { + // Single value is throwing an exception in GetString(), skip it and use the GetValue() instead + if (readerField.Type != typeOfSingle) + { + readerGetValueMethod = typeOfDbDataReader.GetMethod(string.Concat("Get", targetType.Name)); + } + + // If present, then use the property type, otherwise, use the object + if (readerGetValueMethod != null) + { + convertType = targetType; + } + else + { + readerGetValueMethod = typeOfDbDataReader.GetMethod("GetValue"); + convertType = typeof(object); + } + + // Force the conversion flag + isConversionNeeded = true; + } + + // Expressions + var ordinalExpression = Expression.Constant(ordinal); + Expression valueExpression; + + // Check for nullables + if (isNullable) + { + var isDbNullExpression = Expression.Call(readerParameterExpression, typeOfDbDataReader.GetMethod("IsDBNull"), ordinalExpression); + + // True expression + var trueExpression = (Expression)null; + + // Check for nullable + if (underlyingType != null && underlyingType.IsValueType) + { + trueExpression = Expression.New(typeof(Nullable<>).MakeGenericType(targetType)); + } + + // Check if it has been set + if (trueExpression == null) + { + trueExpression = Expression.Default(targetType); + } + + // False expression + var falseExpression = (Expression)Expression.Call(readerParameterExpression, readerGetValueMethod, ordinalExpression); + + // Only if there are conversions, execute the logics inside + if (isConversionNeeded) + { + if (targetType.IsEnum) + { + #region StringToEnum + + if (readerField.Type == typeof(string)) + { + var enumParseMethod = typeof(EnumHelper).GetMethod("Parse", new[] { typeof(Type), typeof(string), typeof(bool) }); + falseExpression = Expression.Call(enumParseMethod, new[] + { + Expression.Constant(propertyType), + falseExpression, + Expression.Constant(true) + }); + var enumPropertyType = targetType; + if (propertyType.IsNullable()) + { + enumPropertyType = typeof(Nullable<>).MakeGenericType(targetType); + } + falseExpression = Expression.Convert(falseExpression, enumPropertyType); + } + + #endregion + + #region ToEnum + + else + { + var enumUnderlyingType = Enum.GetUnderlyingType(targetType); + var enumToObjectMethod = typeof(Enum).GetMethod("ToObject", new[] { typeof(Type), readerField.Type }); + if (readerField.Type == typeof(bool)) + { + falseExpression = Expression.Convert(falseExpression, typeof(object)); + } + falseExpression = Expression.Call(enumToObjectMethod, new[] + { + Expression.Constant(targetType), + falseExpression + }); + falseExpression = Expression.Convert(falseExpression, targetType); + } + + #endregion + } + else + { + #region TimeSpanToDateTime + + if (readerField.Type == typeOfDateTime && targetType == typeOfTimeSpan) + { + falseExpression = Expression.Convert(falseExpression, typeOfDateTime); + } + + #endregion + + #region Default + + else + { + falseExpression = Expression.Convert(falseExpression, targetType); + } + + #endregion + } + + #region DateTimeToTimeSpan + + // In SqLite, the Time column is represented as System.DateTime in .NET. If in any case that the models + // has been designed to have it as System.TimeSpan, then we should somehow be able to set it properly. + + if (readerField.Type == typeOfDateTime && targetType == typeOfTimeSpan) + { + var timeOfDayProperty = typeof(DateTime).GetProperty("TimeOfDay"); + falseExpression = Expression.Property(falseExpression, timeOfDayProperty); + } + + #endregion + } + + // Reset nullable variable + if (underlyingType != null && underlyingType.IsValueType) + { + var setNullable = (targetType.IsEnum == false) || (targetType.IsEnum && readerField.Type != typeof(string)); + if (setNullable) + { + var nullableConstructorExpression = typeof(Nullable<>).MakeGenericType(targetType).GetConstructor(new[] { targetType }); + falseExpression = Expression.New(nullableConstructorExpression, falseExpression); + } + } + + // Set the value + valueExpression = Expression.Condition(isDbNullExpression, trueExpression, falseExpression); + } + else + { + // Call the actual Get/GetValue method by ordinal + valueExpression = Expression.Call(readerParameterExpression, + readerGetValueMethod, + ordinalExpression); + + // Convert to correct type if necessary + if (isConversionNeeded) + { + valueExpression = ConvertValueExpressionForDataEntity(valueExpression, + readerField, + targetType, + convertType); + } + + // Set for the 'Nullable' property + if (underlyingType != null && underlyingType.IsValueType) + { + var setNullable = (targetType.IsEnum == false) || (targetType.IsEnum && readerField.Type != typeof(string)); + if (setNullable) + { + var nullableConstructorExpression = typeof(Nullable<>).MakeGenericType(targetType).GetConstructor(new[] { targetType }); + valueExpression = Expression.New(nullableConstructorExpression, valueExpression); + } + } + } + + // Call convert method if one exists. + if (converterMethod != null) + { + valueExpression = Expression.Call(Expression.Constant(converterMethod.Target), converterMethod.Method, valueExpression); + } + + // Set the actual property value + memberAssignments.Add(Expression.Bind(classProperty.PropertyInfo, valueExpression)); + } + } + + // Return the result + return memberAssignments; + } + private static Expression ConvertValueExpressionForDataEntity(Expression expression, DataReaderField readerField, Type propertyType, diff --git a/RepoDb.Core/RepoDb/RepoDb.csproj b/RepoDb.Core/RepoDb/RepoDb.csproj index e951a603f..eb90e945f 100644 --- a/RepoDb.Core/RepoDb/RepoDb.csproj +++ b/RepoDb.Core/RepoDb/RepoDb.csproj @@ -65,6 +65,7 @@ + From 3069241005d10fddd0c2c6f13ccf053a42bd1a3e Mon Sep 17 00:00:00 2001 From: chrissolutions Date: Sun, 19 Apr 2020 12:02:45 +1200 Subject: [PATCH 2/6] Refactored inner functions that creates column assignment to private methods --- .../RepoDb/Reflection/FunctionCache.cs | 4 +- .../RepoDb/Reflection/FunctionFactory2.cs | 2019 +++++++++++++++++ 2 files changed, 2021 insertions(+), 2 deletions(-) create mode 100644 RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs diff --git a/RepoDb.Core/RepoDb/Reflection/FunctionCache.cs b/RepoDb.Core/RepoDb/Reflection/FunctionCache.cs index e5385c405..514db37d8 100644 --- a/RepoDb.Core/RepoDb/Reflection/FunctionCache.cs +++ b/RepoDb.Core/RepoDb/Reflection/FunctionCache.cs @@ -220,7 +220,7 @@ public static Action Get(string cacheKey, } if (m_cache.TryGetValue(key, out func) == false) { - func = FunctionFactory.GetDataEntityDbCommandParameterSetterFunction(inputFields, outputFields, dbSetting); + func = FunctionFactory2.GetDataEntityDbCommandParameterSetterFunction(inputFields, outputFields, dbSetting); m_cache.TryAdd(key, func); } return func; @@ -284,7 +284,7 @@ public static Action> Get(string cacheKey, var func = (Action>)null; if (m_cache.TryGetValue(key, out func) == false) { - func = FunctionFactory.GetDataEntitiesDbCommandParameterSetterFunction(inputFields, outputFields, batchSize, dbSetting); + func = FunctionFactory2.GetDataEntitiesDbCommandParameterSetterFunction(inputFields, outputFields, batchSize, dbSetting); m_cache.TryAdd(key, func); } return func; diff --git a/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs new file mode 100644 index 000000000..878f318fe --- /dev/null +++ b/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs @@ -0,0 +1,2019 @@ +using RepoDb.Attributes; +using RepoDb.Enumerations; +using RepoDb.Exceptions; +using RepoDb.Extensions; +using RepoDb.Interfaces; +using RepoDb.Resolvers; +using RepoDb.Types; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using RepoDb.Entity; + +namespace RepoDb.Reflection +{ + /// + /// A static factory class used to create a custom function. + /// + internal static class FunctionFactory2 + { + #region SubClasses + + /// + /// A helper class for type enum. + /// + private class EnumHelper + { + /// + /// Parses the string value to a desired enum. It uses the method underneath. + /// + /// The type of enum. + /// The value to parse. + /// The case sensitivity of the parse operation. + /// The enum value. + public static object Parse(Type enumType, + string value, + bool ignoreCase) + { + if (!string.IsNullOrEmpty(value)) + { + return Enum.Parse(enumType?.GetUnderlyingType(), value, ignoreCase); + } + if (enumType.IsNullable()) + { + var nullable = typeof(Nullable<>).MakeGenericType(new[] { enumType }); + return Activator.CreateInstance(nullable); + } + else + { + return Activator.CreateInstance(enumType); + } + } + + + /// + /// Converts the value using the desired convert method (of type ). If not given, it will use the class. + /// + /// The source type. + /// The target type. + /// The value to parse. + /// The converter method to be checked and used. + /// The converted value value. + public static object Convert(Type sourceType, + Type targetType, + object value, + MethodInfo converterMethod) + { + if (value == null) + { + return sourceType.IsNullable() ? null : + targetType.IsValueType ? Activator.CreateInstance(targetType) : null; + } + if (converterMethod != null) + { + return converterMethod.Invoke(null, new[] { value }); + } + else + { + return Converter.DbNullToNull(value) == null ? Activator.CreateInstance(targetType) : + System.Convert.ChangeType(value, targetType); + } + } + } + + private static class Types + { + + // Get the types + public static readonly Type TypeOfDbCommand = typeof(DbCommand); + public static readonly Type TypeOfObject = typeof(object); + public static readonly Type TypeOfDbParameter = typeof(DbParameter); + public static readonly Type TypeOfDbParameterCollection = typeof(DbParameterCollection); + public static readonly Type TypeOfInt = typeof(int); + public static readonly Type TypeOfString = typeof(string); + public static readonly Type TypeOfType = typeof(Type); + public static readonly Type TypeOfPropertyInfo = typeof(PropertyInfo); + public static readonly Type TypeOfBytes = typeof(byte[]); + public static readonly Type TypeOfTimeSpan = typeof(TimeSpan); + public static readonly Type TypeOfBindingFlags = typeof(BindingFlags); + public static readonly Type TypeOfGuid = typeof(Guid); + public static readonly Type TypeOfDateTime = typeof(DateTime); + public static readonly Type TypeOfDecimal = typeof(Decimal); + public static readonly Type TypeOfFloat = typeof(float); + public static readonly Type TypeOfLong = typeof(long); + public static readonly Type TypeOfDouble = typeof(Double); + public static readonly Type TypeOfShort = typeof(short); + public static readonly Type TypeOfBoolean = typeof(bool); + public static readonly Type TypeOfConvert = typeof(Convert); + } + + #endregion + + #region GetDataEntityDbCommandParameterSetterFunction + + /// + /// Gets a compiled function that is used to set the objects of the object based from the values of the data entity/dynamic object. + /// + /// The type of the data entity objects. + /// The list of the input objects. + /// The list of the output objects. + /// The currently in used object. + /// The compiled function. + public static Action GetDataEntityDbCommandParameterSetterFunction(IEnumerable inputFields, + IEnumerable outputFields, + IDbSetting dbSetting) + where TEntity : class + { + // Get the types + var typeOfDbCommand = typeof(DbCommand); + var typeOfEntity = typeof(TEntity); + var typeOfObject = typeof(object); + var typeOfDbParameter = typeof(DbParameter); + var typeOfDbParameterCollection = typeof(DbParameterCollection); + var typeOfInt = typeof(int); + var typeOfString = typeof(string); + var typeOfType = typeof(Type); + var typeOfPropertyInfo = typeof(PropertyInfo); + var typeOfBytes = typeof(byte[]); + var typeOfTimeSpan = typeof(TimeSpan); + var typeOfBindingFlags = typeof(BindingFlags); + var typeOfGuid = typeof(Guid); + var typeOfDateTime = typeof(DateTime); + var typeOfDecimal = typeof(Decimal); + var typeOfFloat = typeof(float); + var typeOfLong = typeof(long); + var typeOfDouble = typeof(Double); + var typeOfShort = typeof(short); + var typeOfBoolean = typeof(bool); + var typeOfConvert = typeof(Convert); + + // Variables for arguments + var commandParameterExpression = Expression.Parameter(typeOfDbCommand, "command"); + var entityParameterExpression = Expression.Parameter(typeOfEntity, "entity"); + + // Variables for types + var entityProperties = PropertyCache.Get(); + + // Variables for DbCommand + var dbCommandParametersProperty = typeOfDbCommand.GetProperty("Parameters"); + var dbCommandCreateParameterMethod = typeOfDbCommand.GetMethod("CreateParameter"); + var dbParameterParameterNameSetMethod = typeOfDbParameter.GetProperty("ParameterName").SetMethod; + var dbParameterValueSetMethod = typeOfDbParameter.GetProperty("Value").SetMethod; + var dbParameterDbTypeSetMethod = typeOfDbParameter.GetProperty("DbType").SetMethod; + var dbParameterDirectionSetMethod = typeOfDbParameter.GetProperty("Direction").SetMethod; + var dbParameterSizeSetMethod = typeOfDbParameter.GetProperty("Size").SetMethod; + var dbParameterPrecisionSetMethod = typeOfDbParameter.GetProperty("Precision").SetMethod; + var dbParameterScaleSetMethod = typeOfDbParameter.GetProperty("Scale").SetMethod; + + // Variables for DbParameterCollection + var dbParameterCollection = Expression.Property(commandParameterExpression, dbCommandParametersProperty); + var dbParameterCollectionAddMethod = typeOfDbParameterCollection.GetMethod("Add", new[] { typeOfObject }); + var dbParameterCollectionClearMethod = typeOfDbParameterCollection.GetMethod("Clear"); + + // Variables for 'Dynamic|Object' object + var objectGetTypeMethod = typeOfObject.GetMethod("GetType"); + var typeGetPropertyMethod = typeOfType.GetMethod("GetProperty", new[] { typeOfString, typeOfBindingFlags }); + var propertyInfoGetValueMethod = typeOfPropertyInfo.GetMethod("GetValue", new[] { typeOfObject }); + + // Other variables + var dbTypeResolver = new ClientTypeToDbTypeResolver(); + + // Variables for the object instance + var propertyVariableList = new List(); + var instanceVariable = Expression.Variable(typeOfEntity, "instance"); + var instanceType = Expression.Constant(typeOfEntity); + var instanceTypeVariable = Expression.Variable(typeOfType, "instanceType"); + + // Input fields properties + if (inputFields?.Any() == true) + { + for (var index = 0; index < inputFields.Count(); index++) + { + propertyVariableList.Add(new + { + Index = index, + Field = inputFields.ElementAt(index), + Direction = ParameterDirection.Input + }); + } + } + + // Output fields properties + if (outputFields?.Any() == true) + { + for (var index = 0; index < outputFields.Count(); index++) + { + propertyVariableList.Add(new + { + Index = inputFields.Count() + index, + Field = outputFields.ElementAt(index), + Direction = ParameterDirection.Output + }); + } + } + + // Variables for expression body + var bodyExpressions = new List(); + + // Clear the parameter collection first + bodyExpressions.Add(Expression.Call(dbParameterCollection, dbParameterCollectionClearMethod)); + + // Get the current instance + var instanceExpressions = new List(); + var instanceVariables = new List {instanceVariable}; + + // Entity instance + instanceExpressions.Add(Expression.Assign(instanceVariable, entityParameterExpression)); + + // Iterate the input fields + foreach (var item in propertyVariableList) + { + #region Field Expressions + + // Property variables + var propertyExpressions = new List(); + var propertyVariables = new List(); + var field = (DbField)item.Field; + var direction = (ParameterDirection)item.Direction; + var propertyIndex = (int)item.Index; + var propertyVariable = (ParameterExpression)null; + var propertyInstance = (Expression)null; + var classProperty = (ClassProperty)null; + var propertyName = field.Name.AsUnquoted(true, dbSetting); + + // Set the proper assignments (property) + if (typeOfEntity == typeOfObject) + { + propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", propertyName)); + propertyInstance = Expression.Call(Expression.Call(instanceVariable, objectGetTypeMethod), + typeGetPropertyMethod, + new[] + { + Expression.Constant(propertyName), + Expression.Constant(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase) + }); + } + else + { + classProperty = entityProperties.First(property => string.Equals(property.GetMappedName().AsUnquoted(true, dbSetting), propertyName.AsUnquoted(true, dbSetting), StringComparison.OrdinalIgnoreCase)); + if (classProperty != null) + { + propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", propertyName)); + propertyInstance = Expression.Property(instanceVariable, classProperty.PropertyInfo); + } + } + + var parameterAssignment = BuildParameterAssignmentExpression( + instanceVariable, + propertyVariable, + field, + classProperty, + (direction == ParameterDirection.Output), + direction, + dbSetting, + dbTypeResolver, + commandParameterExpression, + dbParameterCollection, + dbCommandCreateParameterMethod, + dbParameterCollectionAddMethod, + dbParameterDbTypeSetMethod, + dbParameterDirectionSetMethod, + dbParameterParameterNameSetMethod, + dbParameterScaleSetMethod, + dbParameterSizeSetMethod, + dbParameterValueSetMethod, + dbParameterPrecisionSetMethod, + propertyInfoGetValueMethod + ); + + // Add the necessary variables + if (propertyVariable != null) + { + propertyVariables.Add(propertyVariable); + } + + // Add the necessary expressions + if (propertyVariable != null) + { + propertyExpressions.Add(Expression.Assign(propertyVariable, propertyInstance)); + } + propertyExpressions.Add(parameterAssignment); + + // Add the property block + var propertyBlock = Expression.Block(propertyVariables, propertyExpressions); + + // Add to instance expression + instanceExpressions.Add(propertyBlock); + + #endregion + } + + // Add to the instance block + var instanceBlock = Expression.Block(instanceVariables, instanceExpressions); + + // Add to the body + bodyExpressions.Add(instanceBlock); + + // Set the function value + return Expression + .Lambda>(Expression.Block(bodyExpressions), commandParameterExpression, entityParameterExpression) + .Compile(); + } + + #endregion + + #region GetDataEntitiesDbCommandParameterSetterFunction + + /// + /// Gets a compiled function that is used to set the objects of the object based from the values of the data entity/dynamic objects. + /// + /// The type of the data entity objects. + /// The list of the input objects. + /// The list of the input objects. + /// The batch size of the entity to be passed. + /// The currently in used object. + /// The compiled function. + public static Action> GetDataEntitiesDbCommandParameterSetterFunction( + IEnumerable inputFields, + IEnumerable outputFields, + int batchSize, + IDbSetting dbSetting) + where TEntity : class + { + // Get the types + var typeOfDbCommand = typeof(DbCommand); + var typeOfListEntity = typeof(IList); + var typeOfEntity = typeof(TEntity); + var typeOfObject = typeof(object); + var typeOfDbParameter = typeof(DbParameter); + var typeOfDbParameterCollection = typeof(DbParameterCollection); + var typeOfInt = typeof(int); + var typeOfString = typeof(string); + var typeOfType = typeof(Type); + var typeOfPropertyInfo = typeof(PropertyInfo); + var typeOfTimeSpan = typeof(TimeSpan); + var typeOfBindingFlags = typeof(BindingFlags); + var typeOfGuid = typeof(Guid); + var typeOfDateTime = typeof(DateTime); + var typeOfDecimal = typeof(Decimal); + var typeOfFloat = typeof(float); + var typeOfLong = typeof(long); + var typeOfDouble = typeof(Double); + var typeOfShort = typeof(short); + var typeOfBoolean = typeof(bool); + var typeOfConvert = typeof(Convert); + + // Variables for arguments + var commandParameterExpression = Expression.Parameter(typeOfDbCommand, "command"); + var entitiesParameterExpression = Expression.Parameter(typeOfListEntity, "entities"); + + // Variables for types + var entityProperties = PropertyCache.Get(); + + // Variables for DbCommand + var dbCommandParametersProperty = typeOfDbCommand.GetProperty("Parameters"); + var dbCommandCreateParameterMethod = typeOfDbCommand.GetMethod("CreateParameter"); + var dbParameterParameterNameSetMethod = typeOfDbParameter.GetProperty("ParameterName").SetMethod; + var dbParameterValueSetMethod = typeOfDbParameter.GetProperty("Value").SetMethod; + var dbParameterDbTypeSetMethod = typeOfDbParameter.GetProperty("DbType").SetMethod; + var dbParameterDirectionSetMethod = typeOfDbParameter.GetProperty("Direction").SetMethod; + var dbParameterSizeSetMethod = typeOfDbParameter.GetProperty("Size").SetMethod; + var dbParameterPrecisionSetMethod = typeOfDbParameter.GetProperty("Precision").SetMethod; + var dbParameterScaleSetMethod = typeOfDbParameter.GetProperty("Scale").SetMethod; + + // Variables for DbParameterCollection + var dbParameterCollection = Expression.Property(commandParameterExpression, dbCommandParametersProperty); + var dbParameterCollectionAddMethod = typeOfDbParameterCollection.GetMethod("Add", new[] { typeOfObject }); + var dbParameterCollectionClearMethod = typeOfDbParameterCollection.GetMethod("Clear"); + + // Variables for 'Dynamic|Object' object + var objectGetTypeMethod = typeOfObject.GetMethod("GetType"); + var typeGetPropertyMethod = typeOfType.GetMethod("GetProperty", new[] { typeOfString, typeOfBindingFlags }); + var propertyInfoGetValueMethod = typeOfPropertyInfo.GetMethod("GetValue", new[] { typeOfObject }); + + // Variables for List + var listIndexerMethod = typeOfListEntity.GetMethod("get_Item", new[] { typeOfInt }); + + // Other variables + var dbTypeResolver = new ClientTypeToDbTypeResolver(); + + // Variables for the object instance + var propertyVariableList = new List(); + var instanceVariable = Expression.Variable(typeOfEntity, "instance"); + var instanceType = Expression.Constant(typeOfEntity); // Expression.Call(instanceVariable, objectGetTypeMethod); + var instanceTypeVariable = Expression.Variable(typeOfType, "instanceType"); + + // Input fields properties + if (inputFields?.Any() == true) + { + for (var index = 0; index < inputFields.Count(); index++) + { + propertyVariableList.Add(new + { + Index = index, + Field = inputFields.ElementAt(index), + Direction = ParameterDirection.Input + }); + } + } + + // Output fields properties + if (outputFields?.Any() == true) + { + for (var index = 0; index < outputFields.Count(); index++) + { + propertyVariableList.Add(new + { + Index = inputFields.Count() + index, + Field = outputFields.ElementAt(index), + Direction = ParameterDirection.Output + }); + } + } + + // Variables for expression body + var bodyExpressions = new List(); + + // Clear the parameter collection first + bodyExpressions.Add(Expression.Call(dbParameterCollection, dbParameterCollectionClearMethod)); + + // Iterate by batch size + for (var entityIndex = 0; entityIndex < batchSize; entityIndex++) + { + // Get the current instance + var instance = Expression.Call(entitiesParameterExpression, listIndexerMethod, Expression.Constant(entityIndex)); + var instanceExpressions = new List(); + var instanceVariables = new List(); + + // Entity instance + instanceVariables.Add(instanceVariable); + instanceExpressions.Add(Expression.Assign(instanceVariable, instance)); + + // Iterate the input fields + foreach (var item in propertyVariableList) + { + #region Field Expressions + + // Property variables + var propertyExpressions = new List(); + var propertyVariables = new List(); + var field = (DbField)item.Field; + var direction = (ParameterDirection)item.Direction; + var propertyIndex = (int)item.Index; + var propertyVariable = (ParameterExpression)null; + var propertyInstance = (Expression)null; + var classProperty = (ClassProperty)null; + var propertyName = field.Name.AsUnquoted(true, dbSetting); + + // Set the proper assignments (property) + if (typeOfEntity == typeOfObject) + { + propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", propertyName)); + propertyInstance = Expression.Call(Expression.Call(instanceVariable, objectGetTypeMethod), + typeGetPropertyMethod, + new[] + { + Expression.Constant(propertyName), + Expression.Constant(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase) + }); + } + else + { + classProperty = entityProperties.FirstOrDefault(property => + string.Equals(property.GetMappedName().AsUnquoted(true, dbSetting), propertyName.AsUnquoted(true, dbSetting), StringComparison.OrdinalIgnoreCase)); + if (classProperty != null) + { + propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", propertyName)); + propertyInstance = Expression.Property(instanceVariable, classProperty.PropertyInfo); + } + } + + // Execute the function + var parameterAssignment = BuildParameterAssignmentExpression( + entityIndex /* index */, + instanceVariable /* instance */, + propertyVariable /* property */, + field /* field */, + classProperty /* classProperty */, + (direction == ParameterDirection.Output) /* skipValueAssignment */, + direction /* direction */, + dbSetting, + dbTypeResolver, + commandParameterExpression, + dbParameterCollection, + dbCommandCreateParameterMethod, + dbParameterCollectionAddMethod, + dbParameterDbTypeSetMethod, + dbParameterDirectionSetMethod, + dbParameterParameterNameSetMethod, + dbParameterScaleSetMethod, + dbParameterSizeSetMethod, + dbParameterValueSetMethod, + dbParameterPrecisionSetMethod, + propertyInfoGetValueMethod + ); + + // Add the necessary variables + if (propertyVariable != null) + { + propertyVariables.Add(propertyVariable); + } + + // Add the necessary expressions + if (propertyVariable != null) + { + propertyExpressions.Add(Expression.Assign(propertyVariable, propertyInstance)); + } + propertyExpressions.Add(parameterAssignment); + + // Add the property block + var propertyBlock = Expression.Block(propertyVariables, propertyExpressions); + + // Add to instance expression + instanceExpressions.Add(propertyBlock); + + #endregion + } + + // Add to the instance block + var instanceBlock = Expression.Block(instanceVariables, instanceExpressions); + + // Add to the body + bodyExpressions.Add(instanceBlock); + } + + // Set the function value + return Expression + .Lambda>>(Expression.Block(bodyExpressions), commandParameterExpression, entitiesParameterExpression) + .Compile(); + } + + #endregion + + #region GetDataEntityPropertySetterFromDbCommandParameterFunction + + /// + /// Gets a compiled function that is used to set the data entity object property value based from the value of parameter object. + /// + /// The type of the data entity. + /// The target . + /// The name of the parameter. + /// The index of the batches. + /// The currently in used object. + /// A compiled function that is used to set the data entity object property value based from the value of parameter object. + public static Action GetDataEntityPropertySetterFromDbCommandParameterFunction(Field field, + string parameterName, + int index, + IDbSetting dbSetting) + where TEntity : class + { + // Variables for type + var typeOfEntity = typeof(TEntity); + var typeOfDbCommand = typeof(DbCommand); + var typeOfDbParameterCollection = typeof(DbParameterCollection); + var typeOfString = typeof(string); + var typeOfDbParameter = typeof(DbParameter); + + // Variables for argument + var entityParameterExpression = Expression.Parameter(typeOfEntity, "entity"); + var dbCommandParameterExpression = Expression.Parameter(typeOfDbCommand, "command"); + + // Variables for DbCommand + var dbCommandParametersProperty = typeOfDbCommand.GetProperty("Parameters"); + + // Variables for DbParameterCollection + var dbParameterCollectionIndexerMethod = typeOfDbParameterCollection.GetMethod("get_Item", new[] { typeOfString }); + + // Variables for DbParameter + var dbParameterValueProperty = typeOfDbParameter.GetProperty("Value"); + + // Get the entity property + var propertyName = field.Name.AsUnquoted(true, dbSetting).AsAlphaNumeric(); + var property = (typeOfEntity.GetProperty(propertyName) ?? typeOfEntity.GetPropertyByMapping(propertyName)?.PropertyInfo)?.SetMethod; + + // Get the command parameter + var name = parameterName ?? propertyName; + var parameters = Expression.Property(dbCommandParameterExpression, dbCommandParametersProperty); + var parameter = Expression.Call(parameters, dbParameterCollectionIndexerMethod, + Expression.Constant(index > 0 ? string.Concat(name, "_", index) : name)); + + // Assign the Parameter.Value into DataEntity.Property + var value = Expression.Property(parameter, dbParameterValueProperty); + var propertyAssignment = Expression.Call(entityParameterExpression, property, + Expression.Convert(value, field.Type?.GetUnderlyingType())); + + // Return function + return Expression.Lambda>( + propertyAssignment, entityParameterExpression, dbCommandParameterExpression).Compile(); + } + + #endregion + + #region GetDataEntityPropertyValueSetterFunction + + /// + /// Gets a compiled function that is used to set the data entity object property value. + /// + /// The type of the data entity. + /// The target . + /// A compiled function that is used to set the data entity object property value. + public static Action GetDataEntityPropertyValueSetterFunction(Field field) + where TEntity : class + { + // Variables for type + var typeOfEntity = typeof(TEntity); + var typeOfObject = typeof(object); + var typeOfConverter = typeof(Converter); + + // Variables for argument + var entityParameter = Expression.Parameter(typeOfEntity, "entity"); + var valueParameter = Expression.Parameter(typeOfObject, "value"); + + // Get the entity property + var property = (typeOfEntity.GetProperty(field.Name) ?? typeOfEntity.GetPropertyByMapping(field.Name)?.PropertyInfo)?.SetMethod; + + // Get the converter + var toTypeMethod = typeOfConverter.GetMethod("ToType", new[] { typeOfObject }).MakeGenericMethod(field.Type.GetUnderlyingType()); + + // Assign the value into DataEntity.Property + var propertyAssignment = Expression.Call(entityParameter, property, + Expression.Convert(Expression.Call(toTypeMethod, valueParameter), field.Type)); + + // Return function + return Expression.Lambda>(propertyAssignment, + entityParameter, valueParameter).Compile(); + } + + #endregion + + #region Helpers + + /// + /// Create the parameters based on the list of objects. + /// + /// The target object. + /// The list of the input objects. + /// The list of the output objects. + /// The batch size of the entities to be passed. + internal static void CreateDbCommandParametersFromFields(DbCommand command, + IEnumerable inputFields, + IEnumerable outputFields, + int batchSize) + { + // Variables + var dbTypeResolver = new ClientTypeToDbTypeResolver(); + var typeOfBytes = typeof(byte[]); + var dbSetting = command.Connection.GetDbSetting(); + + // Clear the parameters + command.Parameters.Clear(); + + // Function for each field + var func = new Action((int index, + DbField field, + ParameterDirection direction) => + { + // Create the parameter + var parameter = command.CreateParameter(); + + // Set the property + parameter.ParameterName = field.Name.AsParameter(index, dbSetting); + + // Set the Direction + if (dbSetting.IsDirectionSupported) + { + parameter.Direction = direction; + } + + // Set the DB Type + var dbType = TypeMapper.Get(field.Type?.GetUnderlyingType()); + + // Ensure the type mapping + if (dbType == null) + { + if (field.Type == typeOfBytes) + { + dbType = DbType.Binary; + } + } + + // Resolve manually + if (dbType == null) + { + dbType = dbTypeResolver.Resolve(field.Type); + } + + // Set the DB Type if present + if (dbType != null) + { + parameter.DbType = dbType.Value; + } + + // Set the Size if present + if (field.Size != null) + { + parameter.Size = field.Size.Value; + } + + // Set the Precision if present + if (field.Precision != null) + { + parameter.Precision = field.Precision.Value; + } + + // Set the Scale if present + if (field.Scale != null) + { + parameter.Scale = field.Scale.Value; + } + + // Add the parameter + command.Parameters.Add(parameter); + }); + + for (var index = 0; index < batchSize; index++) + { + // Iterate all the input fields + if (inputFields?.Any() == true) + { + foreach (var field in inputFields) + { + func(index, field, ParameterDirection.Input); + } + } + + // Iterate all the output fields + if (outputFields?.Any() == true) + { + foreach (var field in outputFields) + { + func(index, field, ParameterDirection.Output); + } + } + } + } + + #endregion + + #region Other Data Providers Helpers + + #region SqlServer (System) + + /// + /// Gets the SystemSqlServerTypeMapAttribute if present. + /// + /// The instance of propery to inspect. + /// The instance of SystemSqlServerTypeMapAttribute. + internal static Attribute GetSystemSqlServerTypeMapAttribute(ClassProperty property) + { + return property? + .PropertyInfo + .GetCustomAttributes()? + .FirstOrDefault(e => + e.GetType().FullName.Equals("RepoDb.Attributes.SystemSqlServerTypeMapAttribute")); + } + + /// + /// Gets the value represented by the SystemSqlServerTypeMapAttribute.DbType property. + /// + /// The instance of SystemSqlServerTypeMapAttribute to extract. + /// The value represented by the SystemSqlServerTypeMapAttribute.DbType property. + internal static object GetSystemSqlServerDbTypeFromAttribute(Attribute attribute) + { + if (attribute == null) + { + return null; + } + var type = attribute.GetType(); + return type + .GetProperty("DbType")? + .GetValue(attribute); + } + + /// + /// Gets the system type of System.Data.SqlClient.SqlParameter represented by SystemSqlServerTypeMapAttribute.ParameterType property. + /// + /// The instance of SystemSqlServerTypeMapAttribute to extract. + /// The type of System.Data.SqlClient.SqlParameter represented by SystemSqlServerTypeMapAttribute.ParameterType property. + internal static Type GetSystemSqlServerParameterTypeFromAttribute(Attribute attribute) + { + if (attribute == null) + { + return null; + } + return (Type)attribute + .GetType() + .GetProperty("ParameterType")? + .GetValue(attribute); + } + + /// + /// Gets the instance of represented by the SystemSqlServerTypeMapAttribute.DbType property. + /// + /// The instance of SystemSqlServerTypeMapAttribute to extract. + /// The instance of represented by the SystemSqlServerTypeMapAttribute.DbType property. + internal static MethodInfo GetSystemSqlServerDbTypeFromAttributeSetMethod(Attribute attribute) + { + if (attribute == null) + { + return null; + } + return GetSystemSqlServerParameterTypeFromAttribute(attribute)? + .GetProperty("SqlDbType")? + .SetMethod; + } + + #endregion + + #region SqlServer (Microsoft) + + /// + /// Gets the MicrosoftSqlServerTypeMapAttribute if present. + /// + /// The instance of propery to inspect. + /// The instance of MicrosoftSqlServerTypeMapAttribute. + internal static Attribute GetMicrosoftSqlServerTypeMapAttribute(ClassProperty property) + { + return property? + .PropertyInfo + .GetCustomAttributes()? + .FirstOrDefault(e => + e.GetType().FullName.Equals("RepoDb.Attributes.MicrosoftSqlServerTypeMapAttribute")); + } + + /// + /// Gets the value represented by the MicrosoftSqlServerTypeMapAttribute.DbType property. + /// + /// The instance of MicrosoftSqlServerTypeMapAttribute to extract. + /// The value represented by the MicrosoftSqlServerTypeMapAttribute.DbType property. + internal static object GetMicrosoftSqlServerDbTypeFromAttribute(Attribute attribute) + { + if (attribute == null) + { + return null; + } + var type = attribute.GetType(); + return type + .GetProperty("DbType")? + .GetValue(attribute); + } + + /// + /// Gets the system type of Microsoft.Data.SqlClient.SqlParameter represented by MicrosoftSqlServerTypeMapAttribute.ParameterType property. + /// + /// The instance of MicrosoftSqlServerTypeMapAttribute to extract. + /// The type of Microsoft.Data.SqlClient.SqlParameter represented by MicrosoftSqlServerTypeMapAttribute.ParameterType property. + internal static Type GetMicrosoftSqlServerParameterTypeFromAttribute(Attribute attribute) + { + if (attribute == null) + { + return null; + } + return (Type)attribute + .GetType() + .GetProperty("ParameterType")? + .GetValue(attribute); + } + + /// + /// Gets the instance of represented by the MicrosoftSqlServerTypeMapAttribute.DbType property. + /// + /// The instance of MicrosoftSqlServerTypeMapAttribute to extract. + /// The instance of represented by the MicrosoftSqlServerTypeMapAttribute.DbType property. + internal static MethodInfo GetMicrosoftSqlServerDbTypeFromAttributeSetMethod(Attribute attribute) + { + if (attribute == null) + { + return null; + } + return GetMicrosoftSqlServerParameterTypeFromAttribute(attribute)? + .GetProperty("SqlDbType")? + .SetMethod; + } + + #endregion + + #region MySql + + /// + /// Gets the MySqlTypeMapAttribute if present. + /// + /// The instance of propery to inspect. + /// The instance of MySqlTypeMapAttribute. + internal static Attribute GetMySqlDbTypeTypeMapAttribute(ClassProperty property) + { + return property? + .PropertyInfo + .GetCustomAttributes()? + .FirstOrDefault(e => + e.GetType().FullName.Equals("RepoDb.Attributes.MySqlTypeMapAttribute")); + } + + /// + /// Gets the value represented by the MySqlTypeMapAttribute.DbType property. + /// + /// The instance of MySqlTypeMapAttribute to extract. + /// The value represented by the MySqlTypeMapAttribute.DbType property. + internal static object GetMySqlDbTypeFromAttribute(Attribute attribute) + { + if (attribute == null) + { + return null; + } + var type = attribute.GetType(); + return type + .GetProperty("DbType")? + .GetValue(attribute); + } + + /// + /// Gets the system type of MySql.Data.MySqlClient.MySqlParameter represented by MySqlTypeMapAttribute.ParameterType property. + /// + /// The instance of MySqlTypeMapAttribute to extract. + /// The type of MySql.Data.MySqlClient.MySqlParameter represented by MySqlTypeMapAttribute.ParameterType property. + internal static Type GetMySqlParameterTypeFromAttribute(Attribute attribute) + { + if (attribute == null) + { + return null; + } + return (Type)attribute + .GetType() + .GetProperty("ParameterType")? + .GetValue(attribute); + } + + /// + /// Gets the instance of represented by the MySqlTypeMapAttribute.DbType property. + /// + /// The instance of MySqlTypeMapAttribute to extract. + /// The instance of represented by the MySqlTypeMapAttribute.DbType property. + internal static MethodInfo GetMySqlDbTypeFromAttributeSetMethod(Attribute attribute) + { + if (attribute == null) + { + return null; + } + return GetMySqlParameterTypeFromAttribute(attribute)? + .GetProperty("MySqlDbType")? + .SetMethod; + } + + #endregion + + #region Npgsql + + /// + /// Gets the NpgsqlDbTypeMapAttribute if present. + /// + /// The instance of propery to inspect. + /// The instance of NpgsqlDbTypeMapAttribute. + internal static Attribute GetNpgsqlDbTypeTypeMapAttribute(ClassProperty property) + { + return property? + .PropertyInfo + .GetCustomAttributes()? + .FirstOrDefault(e => + e.GetType().FullName.Equals("RepoDb.Attributes.NpgsqlTypeMapAttribute")); + } + + /// + /// Gets the value represented by the NpgsqlDbTypeMapAttribute.DbType property. + /// + /// The instance of NpgsqlDbTypeMapAttribute to extract. + /// The value represented by the NpgsqlDbTypeMapAttribute.DbType property. + internal static object GetNpgsqlDbTypeFromAttribute(Attribute attribute) + { + if (attribute == null) + { + return null; + } + var type = attribute.GetType(); + return type + .GetProperty("DbType")? + .GetValue(attribute); + } + + /// + /// Gets the system type of NpgsqlTypes.NpgsqlParameter represented by NpgsqlDbTypeMapAttribute.ParameterType property. + /// + /// The instance of NpgsqlDbTypeMapAttribute to extract. + /// The type of NpgsqlTypes.NpgsqlParameter represented by NpgsqlDbTypeMapAttribute.ParameterType property. + internal static Type GetNpgsqlParameterTypeFromAttribute(Attribute attribute) + { + if (attribute == null) + { + return null; + } + return (Type)attribute + .GetType() + .GetProperty("ParameterType")? + .GetValue(attribute); + } + + /// + /// Gets the instance of represented by the NpgsqlDbTypeMapAttribute.DbType property. + /// + /// The instance of NpgsqlDbTypeMapAttribute to extract. + /// The instance of represented by the NpgsqlDbTypeMapAttribute.DbType property. + internal static MethodInfo GetNpgsqlDbTypeFromAttributeSetMethod(Attribute attribute) + { + if (attribute == null) + { + return null; + } + return GetNpgsqlParameterTypeFromAttribute(attribute)? + .GetProperty("NpgsqlDbType")? + .SetMethod; + } + + #endregion + + #endregion + + private static Expression BuildParameterAssignmentExpression + ( + Expression instance, + Expression property, + DbField dbField, + ClassProperty classProperty, + bool skipValueAssignment, + ParameterDirection direction, + IDbSetting dbSetting, + ClientTypeToDbTypeResolver dbTypeResolver, + Expression commandParameterExpression, + Expression dbParameterCollection, + MethodInfo dbCommandCreateParameterMethod, + MethodInfo dbParameterCollectionAddMethod, + MethodInfo dbParameterDbTypeSetMethod, + MethodInfo dbParameterDirectionSetMethod, + MethodInfo dbParameterParameterNameSetMethod, + MethodInfo dbParameterScaleSetMethod, + MethodInfo dbParameterSizeSetMethod, + MethodInfo dbParameterValueSetMethod, + MethodInfo dbParameterPrecisionSetMethod, + MethodInfo propertyInfoGetValueMethod + ) where TEntity : class + { + // Parameters for the block + var parameterAssignments = new List(); + + // Parameter variables + var parameterName = dbField.Name.AsUnquoted(true, dbSetting).AsAlphaNumeric(); + var parameterVariable = Expression.Variable(Types.TypeOfDbParameter, string.Concat("parameter", parameterName)); + var parameterInstance = Expression.Call(commandParameterExpression, dbCommandCreateParameterMethod); + parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); + + // Set the name + var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, Expression.Constant(parameterName)); + parameterAssignments.Add(nameAssignment); + + // Property instance + var instanceProperty = (PropertyInfo)null; + var propertyType = (Type)null; + var fieldType = dbField.Type?.GetUnderlyingType(); + + // Property handlers + var handlerInstance = (object)null; + var handlerSetMethod = (MethodInfo)null; + + #region Value + + // Set the value + if (skipValueAssignment == false) + { + // Set the value + var valueAssignment = (Expression)null; + + // Check the proper type of the entity + if (typeof(TEntity) != Types.TypeOfObject && typeof(TEntity).IsGenericType == false) + { + instanceProperty = classProperty.PropertyInfo; // typeOfEntity.GetProperty(classProperty.PropertyInfo.Name); + } + + #region PropertyHandler + + var propertyHandlerAttribute = instanceProperty?.GetCustomAttribute(); + + if (propertyHandlerAttribute != null) + { + // Get from the attribute + handlerInstance = PropertyHandlerCache.Get(classProperty.PropertyInfo); + handlerSetMethod = propertyHandlerAttribute.HandlerType.GetMethod("Set"); + } + else + { + // Get from the type level mappings (DB type) + handlerInstance = PropertyHandlerMapper.Get(dbField.Type.GetUnderlyingType()); + if (handlerInstance != null) + { + handlerSetMethod = handlerInstance.GetType().GetMethod("Set"); + } + } + + #endregion + + #region Instance.Property or PropertyInfo.GetValue() + + // Set the value + var value = (Expression)null; + + // If the property is missing directly, then it could be a dynamic object + if (instanceProperty == null) + { + value = Expression.Call(property, propertyInfoGetValueMethod, instance); + } + else + { + propertyType = instanceProperty.PropertyType.GetUnderlyingType(); + + if (handlerInstance == null) + { + if (Converter.ConversionType == ConversionType.Automatic) + { + var valueToConvert = Expression.Property(instance, instanceProperty); + + #region StringToGuid + + // Create a new guid here + if (propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */) + { + value = Expression.New(Types.TypeOfGuid.GetConstructor(new[] { Types.TypeOfString }), new[] { valueToConvert }); + } + + #endregion + + #region GuidToString + + // Call the System.Convert conversion + else if (propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString/* GuidToString*/) + { + var convertMethod = typeof(Convert).GetMethod("ToString", new[] { Types.TypeOfObject }); + value = Expression.Call(convertMethod, Expression.Convert(valueToConvert, Types.TypeOfObject)); + value = Expression.Convert(value, fieldType); + } + + #endregion + + else + { + value = valueToConvert; + } + } + else + { + // Get the Class.Property + value = Expression.Property(instance, instanceProperty); + } + + #region EnumAsIntForString + + if (propertyType.IsEnum) + { + var convertToTypeMethod = (MethodInfo)null; + if (convertToTypeMethod == null) + { + var mappedToType = classProperty?.GetDbType(); + if (mappedToType == null) + { + mappedToType = new ClientTypeToDbTypeResolver().Resolve(dbField.Type); + } + if (mappedToType != null) + { + convertToTypeMethod = Types.TypeOfConvert.GetMethod(string.Concat("To", mappedToType.ToString()), new[] { Types.TypeOfObject }); + } + } + if (convertToTypeMethod == null) + { + convertToTypeMethod = Types.TypeOfConvert.GetMethod(string.Concat("To", dbField.Type.Name), new[] { Types.TypeOfObject }); + } + if (convertToTypeMethod == null) + { + throw new ConverterNotFoundException($"The convert between '{propertyType.FullName}' and database type '{dbField.DatabaseType}' (of .NET CLR '{dbField.Type.FullName}') is not found."); + } + else + { + var converterMethod = typeof(EnumHelper).GetMethod("Convert"); + if (converterMethod != null) + { + value = Expression.Call(converterMethod, + Expression.Constant(instanceProperty.PropertyType), + Expression.Constant(dbField.Type), + Expression.Convert(value, Types.TypeOfObject), + Expression.Constant(convertToTypeMethod)); + } + } + } + + #endregion + } + else + { + // Get the value directly from the property + value = Expression.Property(instance, instanceProperty); + + #region PropertyHandler + + if (handlerInstance != null) + { + var setParameter = handlerSetMethod.GetParameters().First(); + value = Expression.Call(Expression.Constant(handlerInstance), + handlerSetMethod, + Expression.Convert(value, setParameter.ParameterType), + Expression.Constant(classProperty)); + } + + #endregion + } + + // Convert to object + value = Expression.Convert(value, Types.TypeOfObject); + } + + // Declare the variable for the value assignment + var valueBlock = (Expression)null; + var isNullable = dbField.IsNullable == true || + instanceProperty == null || + ( + instanceProperty != null && + ( + instanceProperty.PropertyType.IsValueType == false || + Nullable.GetUnderlyingType(instanceProperty.PropertyType) != null + ) + ); + + // The value for DBNull.Value + var dbNullValue = Expression.Convert(Expression.Constant(DBNull.Value), Types.TypeOfObject); + + // Check if the property is nullable + if (isNullable == true) + { + // Identification of the DBNull + var valueVariable = Expression.Variable(Types.TypeOfObject, string.Concat("valueOf", parameterName)); + var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); + + // Set the propert value + valueBlock = Expression.Block(new[] { valueVariable }, + Expression.Assign(valueVariable, value), + Expression.Condition(valueIsNull, dbNullValue, valueVariable)); + } + else + { + valueBlock = value; + } + + // Add to the collection + valueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, valueBlock); + + #endregion + + // Check if it is a direct assignment or not + if (typeof(TEntity) != Types.TypeOfObject) + { + parameterAssignments.Add(valueAssignment); + } + else + { + var dbNullValueAssignment = (Expression)null; + + #region DBNull.Value + + // Set the default type value + if (dbField.IsNullable == false && dbField.Type != null) + { + dbNullValueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, + Expression.Convert(Expression.Default(dbField.Type), Types.TypeOfObject)); + } + + // Set the DBNull value + if (dbNullValueAssignment == null) + { + dbNullValueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, dbNullValue); + } + + #endregion + + // Check the presence of the property + var propertyIsNull = Expression.Equal(property, Expression.Constant(null)); + + // Add to parameter assignment + parameterAssignments.Add(Expression.Condition(propertyIsNull, dbNullValueAssignment, valueAssignment)); + } + } + + #endregion + + #region DbType + + #region DbType + + // Set for non Timestamp, not-working in System.Data.SqlClient but is working at Microsoft.Data.SqlClient + // It is actually me who file this issue to Microsoft :) + //if (fieldOrPropertyType != typeOfTimeSpan) + //{ + // Identify the DB Type + var fieldOrPropertyType = (Type)null; + var dbType = (DbType?)null; + + // Identify the conversion + if (Converter.ConversionType == ConversionType.Automatic) + { + // Identity the conversion + if (propertyType == Types.TypeOfDateTime && fieldType == Types.TypeOfString /* DateTimeToString */ || + propertyType == Types.TypeOfDecimal && (fieldType == Types.TypeOfFloat || fieldType == Types.TypeOfDouble) /* DecimalToFloat/DecimalToDouble */ || + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfLong /* DoubleToBigint */|| + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfInt /* DoubleToBigint */ || + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfShort /* DoubleToShort */|| + propertyType == Types.TypeOfFloat && fieldType == Types.TypeOfLong /* FloatToBigint */ || + propertyType == Types.TypeOfFloat && fieldType == Types.TypeOfShort /* FloatToShort */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDateTime /* StringToDate */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfShort /* StringToShort */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfInt /* StringToInt */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfLong /* StringToLong */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDouble /* StringToDouble */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDecimal /* StringToDecimal */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfFloat /* StringToFloat */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfBoolean /* StringToBoolean */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */ || + propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString /* GuidToString */) + { + fieldOrPropertyType = fieldType; + } + else if (propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString /* UniqueIdentifierToString */) + { + fieldOrPropertyType = propertyType; + } + if (fieldOrPropertyType != null) + { + dbType = dbTypeResolver.Resolve(fieldOrPropertyType); + } + } + + // Get the class property + if (dbType == null && handlerInstance == null) + { + if (fieldOrPropertyType != typeof(SqlVariant) && !string.Equals(dbField.DatabaseType, "sql_variant", StringComparison.OrdinalIgnoreCase)) + { + dbType = classProperty?.GetDbType(); + } + } + + // Set to normal if null + if (fieldOrPropertyType == null) + { + fieldOrPropertyType = dbField.Type?.GetUnderlyingType() ?? instanceProperty?.PropertyType.GetUnderlyingType(); + } + + if (fieldOrPropertyType != null) + { + // Get the type mapping + if (dbType == null) + { + dbType = TypeMapper.Get(fieldOrPropertyType); + } + + // Use the resolver + if (dbType == null) + { + dbType = dbTypeResolver.Resolve(fieldOrPropertyType); + } + } + + // Set the DB Type + if (dbType != null) + { + var dbTypeAssignment = Expression.Call(parameterVariable, dbParameterDbTypeSetMethod, Expression.Constant(dbType)); + parameterAssignments.Add(dbTypeAssignment); + } + //} + + #endregion + + #region SqlDbType (System) + + // Get the SqlDbType value from SystemSqlServerTypeMapAttribute + var systemSqlServerTypeMapAttribute = GetSystemSqlServerTypeMapAttribute(classProperty); + if (systemSqlServerTypeMapAttribute != null) + { + var systemSqlDbTypeValue = GetSystemSqlServerDbTypeFromAttribute(systemSqlServerTypeMapAttribute); + var systemSqlParameterType = GetSystemSqlServerParameterTypeFromAttribute(systemSqlServerTypeMapAttribute); + var dbParameterSystemSqlDbTypeSetMethod = GetSystemSqlServerDbTypeFromAttributeSetMethod(systemSqlServerTypeMapAttribute); + var systemSqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, systemSqlParameterType), + dbParameterSystemSqlDbTypeSetMethod, + Expression.Constant(systemSqlDbTypeValue)); + parameterAssignments.Add(systemSqlDbTypeAssignment); + } + + #endregion + + #region SqlDbType (Microsoft) + + // Get the SqlDbType value from MicrosoftSqlServerTypeMapAttribute + var microsoftSqlServerTypeMapAttribute = GetMicrosoftSqlServerTypeMapAttribute(classProperty); + if (microsoftSqlServerTypeMapAttribute != null) + { + var microsoftSqlDbTypeValue = GetMicrosoftSqlServerDbTypeFromAttribute(microsoftSqlServerTypeMapAttribute); + var microsoftSqlParameterType = GetMicrosoftSqlServerParameterTypeFromAttribute(microsoftSqlServerTypeMapAttribute); + var dbParameterMicrosoftSqlDbTypeSetMethod = GetMicrosoftSqlServerDbTypeFromAttributeSetMethod(microsoftSqlServerTypeMapAttribute); + var microsoftSqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, microsoftSqlParameterType), + dbParameterMicrosoftSqlDbTypeSetMethod, + Expression.Constant(microsoftSqlDbTypeValue)); + parameterAssignments.Add(microsoftSqlDbTypeAssignment); + } + + #endregion + + #region MySqlDbType + + // Get the MySqlDbType value from MySqlDbTypeAttribute + var mysqlDbTypeTypeMapAttribute = GetMySqlDbTypeTypeMapAttribute(classProperty); + if (mysqlDbTypeTypeMapAttribute != null) + { + var mySqlDbTypeValue = GetMySqlDbTypeFromAttribute(mysqlDbTypeTypeMapAttribute); + var mySqlParameterType = GetMySqlParameterTypeFromAttribute(mysqlDbTypeTypeMapAttribute); + var dbParameterMySqlDbTypeSetMethod = GetMySqlDbTypeFromAttributeSetMethod(mysqlDbTypeTypeMapAttribute); + var mySqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, mySqlParameterType), + dbParameterMySqlDbTypeSetMethod, + Expression.Constant(mySqlDbTypeValue)); + parameterAssignments.Add(mySqlDbTypeAssignment); + } + + #endregion + + #region NpgsqlDbType + + // Get the NpgsqlDbType value from NpgsqlTypeMapAttribute + var npgsqlDbTypeTypeMapAttribute = GetNpgsqlDbTypeTypeMapAttribute(classProperty); + if (npgsqlDbTypeTypeMapAttribute != null) + { + var npgsqlDbTypeValue = GetNpgsqlDbTypeFromAttribute(npgsqlDbTypeTypeMapAttribute); + var npgsqlParameterType = GetNpgsqlParameterTypeFromAttribute(npgsqlDbTypeTypeMapAttribute); + var dbParameterNpgsqlDbTypeSetMethod = GetNpgsqlDbTypeFromAttributeSetMethod(npgsqlDbTypeTypeMapAttribute); + var npgsqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, npgsqlParameterType), + dbParameterNpgsqlDbTypeSetMethod, + Expression.Constant(npgsqlDbTypeValue)); + parameterAssignments.Add(npgsqlDbTypeAssignment); + } + + #endregion + + #endregion + + #region Direction + + if (dbSetting.IsDirectionSupported) + { + // Set the Parameter Direction + var directionAssignment = Expression.Call(parameterVariable, dbParameterDirectionSetMethod, Expression.Constant(direction)); + parameterAssignments.Add(directionAssignment); + } + + #endregion + + #region Size + + // Set only for non-image + // By default, SQL Server only put (16 size), and that would fail if the user + // used this type for their binary columns and assign a much longer values + //if (!string.Equals(field.DatabaseType, "image", StringComparison.OrdinalIgnoreCase)) + //{ + // Set the Size + if (dbField.Size != null) + { + var sizeAssignment = Expression.Call(parameterVariable, dbParameterSizeSetMethod, Expression.Constant(dbField.Size.Value)); + parameterAssignments.Add(sizeAssignment); + } + //} + + #endregion + + #region Precision + + // Set the Precision + if (dbField.Precision != null) + { + var precisionAssignment = Expression.Call(parameterVariable, dbParameterPrecisionSetMethod, Expression.Constant(dbField.Precision.Value)); + parameterAssignments.Add(precisionAssignment); + } + + #endregion + + #region Scale + + // Set the Scale + if (dbField.Scale != null) + { + var scaleAssignment = Expression.Call(parameterVariable, dbParameterScaleSetMethod, Expression.Constant(dbField.Scale.Value)); + parameterAssignments.Add(scaleAssignment); + } + + #endregion + + // Add the actual addition + parameterAssignments.Add(Expression.Call(dbParameterCollection, dbParameterCollectionAddMethod, parameterVariable)); + + // Return the value + return Expression.Block(new[] { parameterVariable }, parameterAssignments); + } + + private static Expression BuildParameterAssignmentExpression + ( + int entityIndex, + Expression instance, + Expression property, + DbField dbField, + ClassProperty classProperty, + bool skipValueAssignment, + ParameterDirection direction, + IDbSetting dbSetting, + ClientTypeToDbTypeResolver dbTypeResolver, + Expression commandParameterExpression, + Expression dbParameterCollection, + MethodInfo dbCommandCreateParameterMethod, + MethodInfo dbParameterCollectionAddMethod, + MethodInfo dbParameterDbTypeSetMethod, + MethodInfo dbParameterDirectionSetMethod, + MethodInfo dbParameterParameterNameSetMethod, + MethodInfo dbParameterScaleSetMethod, + MethodInfo dbParameterSizeSetMethod, + MethodInfo dbParameterValueSetMethod, + MethodInfo dbParameterPrecisionSetMethod, + MethodInfo propertyInfoGetValueMethod + ) where TEntity : class + { + // Parameters for the block + var parameterAssignments = new List(); + + // Parameter variables + var parameterName = dbField.Name.AsUnquoted(true, dbSetting).AsAlphaNumeric(); + var parameterVariable = Expression.Variable(Types.TypeOfDbParameter, string.Concat("parameter", parameterName)); + var parameterInstance = Expression.Call(commandParameterExpression, dbCommandCreateParameterMethod); + parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); + + // Set the name + var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, + Expression.Constant(entityIndex > 0 ? string.Concat(parameterName, "_", entityIndex) : parameterName)); + parameterAssignments.Add(nameAssignment); + + // Property instance + var instanceProperty = (PropertyInfo)null; + var propertyType = (Type)null; + var fieldType = dbField.Type?.GetUnderlyingType(); + + // Property handlers + var handlerInstance = (object)null; + var handlerSetMethod = (MethodInfo)null; + + #region Value + + // Set the value + if (skipValueAssignment == false) + { + // Set the value + var valueAssignment = (Expression)null; + + // Check the proper type of the entity + if (typeof(TEntity) != Types.TypeOfObject && typeof(TEntity).IsGenericType == false) + { + instanceProperty = classProperty.PropertyInfo; // typeOfEntity.GetProperty(classProperty.PropertyInfo.Name); + } + + #region PropertyHandler + + var propertyHandlerAttribute = instanceProperty?.GetCustomAttribute(); + + if (propertyHandlerAttribute != null) + { + // Get from the attribute + handlerInstance = PropertyHandlerCache.Get(classProperty.PropertyInfo); + handlerSetMethod = propertyHandlerAttribute.HandlerType.GetMethod("Set"); + } + else + { + // Get from the type level mappings (DB type) + handlerInstance = PropertyHandlerMapper.Get(dbField.Type.GetUnderlyingType()); + if (handlerInstance != null) + { + handlerSetMethod = handlerInstance.GetType().GetMethod("Set"); + } + } + + #endregion + + #region Instance.Property or PropertyInfo.GetValue() + + // Set the value + var value = (Expression)null; + + // If the property is missing directly, then it could be a dynamic object + if (instanceProperty == null) + { + value = Expression.Call(property, propertyInfoGetValueMethod, instance); + } + else + { + propertyType = instanceProperty?.PropertyType.GetUnderlyingType(); + + if (handlerInstance == null) + { + if (Converter.ConversionType == ConversionType.Automatic) + { + var valueToConvert = Expression.Property(instance, instanceProperty); + + #region StringToGuid + + // Create a new guid here + if (propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */) + { + value = Expression.New(Types.TypeOfGuid.GetConstructor(new[] { Types.TypeOfString }), new[] { valueToConvert }); + } + + #endregion + + #region GuidToString + + // Call the System.Convert conversion + else if (propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString/* GuidToString*/) + { + var convertMethod = typeof(Convert).GetMethod("ToString", new[] { Types.TypeOfObject }); + value = Expression.Call(convertMethod, Expression.Convert(valueToConvert, Types.TypeOfObject)); + value = Expression.Convert(value, fieldType); + } + + #endregion + + else + { + value = valueToConvert; + } + } + else + { + // Get the Class.Property + value = Expression.Property(instance, instanceProperty); + } + + #region EnumAsIntForString + + if (propertyType.IsEnum) + { + var convertToTypeMethod = (MethodInfo)null; + if (convertToTypeMethod == null) + { + var mappedToType = classProperty?.GetDbType(); + if (mappedToType == null) + { + mappedToType = new ClientTypeToDbTypeResolver().Resolve(dbField.Type); + } + if (mappedToType != null) + { + convertToTypeMethod = Types.TypeOfConvert.GetMethod(string.Concat("To", mappedToType.ToString()), new[] { Types.TypeOfObject }); + } + } + if (convertToTypeMethod == null) + { + convertToTypeMethod = Types.TypeOfConvert.GetMethod(string.Concat("To", dbField.Type.Name), new[] { Types.TypeOfObject }); + } + if (convertToTypeMethod == null) + { + throw new ConverterNotFoundException($"The convert between '{propertyType.FullName}' and database type '{dbField.DatabaseType}' (of .NET CLR '{dbField.Type.FullName}') is not found."); + } + else + { + var converterMethod = typeof(EnumHelper).GetMethod("Convert"); + if (converterMethod != null) + { + value = Expression.Call(converterMethod, + Expression.Constant(instanceProperty.PropertyType), + Expression.Constant(dbField.Type), + Expression.Convert(value, Types.TypeOfObject), + Expression.Constant(convertToTypeMethod)); + } + } + } + + #endregion + } + else + { + // Get the value directly from the property + value = Expression.Property(instance, instanceProperty); + + #region PropertyHandler + + if (handlerInstance != null) + { + var setParameter = handlerSetMethod.GetParameters().First(); + value = Expression.Call(Expression.Constant(handlerInstance), + handlerSetMethod, + Expression.Convert(value, setParameter.ParameterType), + Expression.Constant(classProperty)); + } + + #endregion + } + + // Convert to object + value = Expression.Convert(value, Types.TypeOfObject); + } + + // Declare the variable for the value assignment + var valueBlock = (Expression)null; + var isNullable = dbField.IsNullable == true || + instanceProperty == null || + ( + instanceProperty != null && + ( + instanceProperty.PropertyType.IsValueType == false || + Nullable.GetUnderlyingType(instanceProperty.PropertyType) != null + ) + ); + + // The value for DBNull.Value + var dbNullValue = Expression.Convert(Expression.Constant(DBNull.Value), Types.TypeOfObject); + + // Check if the property is nullable + if (isNullable == true) + { + // Identification of the DBNull + var valueVariable = Expression.Variable(Types.TypeOfObject, string.Concat("valueOf", parameterName)); + var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); + + // Set the propert value + valueBlock = Expression.Block(new[] { valueVariable }, + Expression.Assign(valueVariable, value), + Expression.Condition(valueIsNull, dbNullValue, valueVariable)); + } + else + { + valueBlock = value; + } + + // Add to the collection + valueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, valueBlock); + + #endregion + + // Check if it is a direct assignment or not + if (typeof(TEntity) != Types.TypeOfObject) + { + parameterAssignments.Add(valueAssignment); + } + else + { + var dbNullValueAssignment = (Expression)null; + + #region DBNull.Value + + // Set the default type value + if (dbField.IsNullable == false && dbField.Type != null) + { + dbNullValueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, + Expression.Convert(Expression.Default(dbField.Type), Types.TypeOfObject)); + } + + // Set the DBNull value + if (dbNullValueAssignment == null) + { + dbNullValueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, dbNullValue); + } + + #endregion + + // Check the presence of the property + var propertyIsNull = Expression.Equal(property, Expression.Constant(null)); + + // Add to parameter assignment + parameterAssignments.Add(Expression.Condition(propertyIsNull, dbNullValueAssignment, valueAssignment)); + } + } + + #endregion + + #region DbType + + #region DbType + + // Set for non Timestamp, not-working in System.Data.SqlClient but is working at Microsoft.Data.SqlClient + // It is actually me who file this issue to Microsoft :) + //if (fieldOrPropertyType != typeOfTimeSpan) + //{ + // Identify the DB Type + var fieldOrPropertyType = (Type)null; + var dbType = (DbType?)null; + + // Identify the conversion + if (Converter.ConversionType == ConversionType.Automatic) + { + // Identity the conversion + if (propertyType == Types.TypeOfDateTime && fieldType == Types.TypeOfString /* DateTimeToString */ || + propertyType == Types.TypeOfDecimal && (fieldType == Types.TypeOfFloat || fieldType == Types.TypeOfDouble) /* DecimalToFloat/DecimalToDouble */ || + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfLong /* DoubleToBigint */|| + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfInt /* DoubleToBigint */ || + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfShort /* DoubleToShort */|| + propertyType == Types.TypeOfFloat && fieldType == Types.TypeOfLong /* FloatToBigint */ || + propertyType == Types.TypeOfFloat && fieldType == Types.TypeOfShort /* FloatToShort */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDateTime /* StringToDate */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfShort /* StringToShort */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfInt /* StringToInt */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfLong /* StringToLong */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDouble /* StringToDouble */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDecimal /* StringToDecimal */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfFloat /* StringToFloat */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfBoolean /* StringToBoolean */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */ || + propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString /* GuidToString */) + { + fieldOrPropertyType = fieldType; + } + else if (propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString /* UniqueIdentifierToString */) + { + fieldOrPropertyType = propertyType; + } + if (fieldOrPropertyType != null) + { + dbType = dbTypeResolver.Resolve(fieldOrPropertyType); + } + } + + // Get the class property + if (dbType == null && handlerInstance == null) + { + if (fieldOrPropertyType != typeof(SqlVariant) && !string.Equals(dbField.DatabaseType, "sql_variant", StringComparison.OrdinalIgnoreCase)) + { + dbType = classProperty?.GetDbType(); + } + } + + // Set to normal if null + if (fieldOrPropertyType == null) + { + fieldOrPropertyType = dbField.Type?.GetUnderlyingType() ?? instanceProperty?.PropertyType.GetUnderlyingType(); + } + + if (fieldOrPropertyType != null) + { + // Get the type mapping + if (dbType == null) + { + dbType = TypeMapper.Get(fieldOrPropertyType); + } + + // Use the resolver + if (dbType == null) + { + dbType = dbTypeResolver.Resolve(fieldOrPropertyType); + } + } + + // Set the DB Type + if (dbType != null) + { + var dbTypeAssignment = Expression.Call(parameterVariable, dbParameterDbTypeSetMethod, Expression.Constant(dbType)); + parameterAssignments.Add(dbTypeAssignment); + } + //} + + #endregion + + #region SqlDbType (System) + + // Get the SqlDbType value from SystemSqlServerTypeMapAttribute + var systemSqlServerTypeMapAttribute = GetSystemSqlServerTypeMapAttribute(classProperty); + if (systemSqlServerTypeMapAttribute != null) + { + var systemSqlDbTypeValue = GetSystemSqlServerDbTypeFromAttribute(systemSqlServerTypeMapAttribute); + var systemSqlParameterType = GetSystemSqlServerParameterTypeFromAttribute(systemSqlServerTypeMapAttribute); + var dbParameterSystemSqlDbTypeSetMethod = GetSystemSqlServerDbTypeFromAttributeSetMethod(systemSqlServerTypeMapAttribute); + var systemSqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, systemSqlParameterType), + dbParameterSystemSqlDbTypeSetMethod, + Expression.Constant(systemSqlDbTypeValue)); + parameterAssignments.Add(systemSqlDbTypeAssignment); + } + + #endregion + + #region SqlDbType (Microsoft) + + // Get the SqlDbType value from MicrosoftSqlServerTypeMapAttribute + var microsoftSqlServerTypeMapAttribute = GetMicrosoftSqlServerTypeMapAttribute(classProperty); + if (microsoftSqlServerTypeMapAttribute != null) + { + var microsoftSqlDbTypeValue = GetMicrosoftSqlServerDbTypeFromAttribute(microsoftSqlServerTypeMapAttribute); + var microsoftSqlParameterType = GetMicrosoftSqlServerParameterTypeFromAttribute(microsoftSqlServerTypeMapAttribute); + var dbParameterMicrosoftSqlDbTypeSetMethod = GetMicrosoftSqlServerDbTypeFromAttributeSetMethod(microsoftSqlServerTypeMapAttribute); + var microsoftSqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, microsoftSqlParameterType), + dbParameterMicrosoftSqlDbTypeSetMethod, + Expression.Constant(microsoftSqlDbTypeValue)); + parameterAssignments.Add(microsoftSqlDbTypeAssignment); + } + + #endregion + + #region MySqlDbType + + // Get the MySqlDbType value from MySqlDbTypeAttribute + var mysqlDbTypeTypeMapAttribute = GetMySqlDbTypeTypeMapAttribute(classProperty); + if (mysqlDbTypeTypeMapAttribute != null) + { + var mySqlDbTypeValue = GetMySqlDbTypeFromAttribute(mysqlDbTypeTypeMapAttribute); + var mySqlParameterType = GetMySqlParameterTypeFromAttribute(mysqlDbTypeTypeMapAttribute); + var dbParameterMySqlDbTypeSetMethod = GetMySqlDbTypeFromAttributeSetMethod(mysqlDbTypeTypeMapAttribute); + var mySqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, mySqlParameterType), + dbParameterMySqlDbTypeSetMethod, + Expression.Constant(mySqlDbTypeValue)); + parameterAssignments.Add(mySqlDbTypeAssignment); + } + + #endregion + + #region NpgsqlDbType + + // Get the NpgsqlDbType value from NpgsqlTypeMapAttribute + var npgsqlDbTypeTypeMapAttribute = GetNpgsqlDbTypeTypeMapAttribute(classProperty); + if (npgsqlDbTypeTypeMapAttribute != null) + { + var npgsqlDbTypeValue = GetNpgsqlDbTypeFromAttribute(npgsqlDbTypeTypeMapAttribute); + var npgsqlParameterType = GetNpgsqlParameterTypeFromAttribute(npgsqlDbTypeTypeMapAttribute); + var dbParameterNpgsqlDbTypeSetMethod = GetNpgsqlDbTypeFromAttributeSetMethod(npgsqlDbTypeTypeMapAttribute); + var npgsqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, npgsqlParameterType), + dbParameterNpgsqlDbTypeSetMethod, + Expression.Constant(npgsqlDbTypeValue)); + parameterAssignments.Add(npgsqlDbTypeAssignment); + } + + #endregion + + #endregion + + #region Direction + + if (dbSetting.IsDirectionSupported) + { + // Set the Parameter Direction + var directionAssignment = Expression.Call(parameterVariable, dbParameterDirectionSetMethod, Expression.Constant(direction)); + parameterAssignments.Add(directionAssignment); + } + + #endregion + + #region Size + + // Set only for non-image + // By default, SQL Server only put (16 size), and that would fail if the user + // used this type for their binary columns and assign a much longer values + //if (!string.Equals(field.DatabaseType, "image", StringComparison.OrdinalIgnoreCase)) + //{ + // Set the Size + if (dbField.Size != null) + { + var sizeAssignment = Expression.Call(parameterVariable, dbParameterSizeSetMethod, Expression.Constant(dbField.Size.Value)); + parameterAssignments.Add(sizeAssignment); + } + //} + + #endregion + + #region Precision + + // Set the Precision + if (dbField.Precision != null) + { + var precisionAssignment = Expression.Call(parameterVariable, dbParameterPrecisionSetMethod, Expression.Constant(dbField.Precision.Value)); + parameterAssignments.Add(precisionAssignment); + } + + #endregion + + #region Scale + + // Set the Scale + if (dbField.Scale != null) + { + var scaleAssignment = Expression.Call(parameterVariable, dbParameterScaleSetMethod, Expression.Constant(dbField.Scale.Value)); + parameterAssignments.Add(scaleAssignment); + } + + #endregion + + // Add the actual addition + parameterAssignments.Add(Expression.Call(dbParameterCollection, dbParameterCollectionAddMethod, parameterVariable)); + + // Return the value + return Expression.Block(new[] { parameterVariable }, parameterAssignments); + } + } +} From 99fa63076f701ae3a1a2b89edb5d39249acbe15d Mon Sep 17 00:00:00 2001 From: chrissolutions Date: Sun, 19 Apr 2020 14:17:39 +1200 Subject: [PATCH 3/6] More refactoring of FactoryFunction2 moving static methodinfos to Types class. --- .../RepoDb/Extensions/EnumerableExtension.cs | 14 + .../RepoDb/Reflection/FunctionFactory2.cs | 323 ++++++------------ 2 files changed, 120 insertions(+), 217 deletions(-) diff --git a/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs b/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs index 13db03d67..cf237ea02 100644 --- a/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs +++ b/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs @@ -82,6 +82,20 @@ public static T[] AsArray(this IEnumerable value) return value is T[]? (T[])value : value.ToArray(); } + /// + /// For each extension. + /// + /// item type + /// collection + /// iterative action + public static void ForEach(this IEnumerable collection, Action eachAction) + { + foreach (var item in collection) + { + eachAction?.Invoke(item); + } + } + /// /// For each extension. /// diff --git a/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs index 878f318fe..5a4b055aa 100644 --- a/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs +++ b/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs @@ -88,19 +88,16 @@ public static object Convert(Type sourceType, private static class Types { - // Get the types public static readonly Type TypeOfDbCommand = typeof(DbCommand); public static readonly Type TypeOfObject = typeof(object); public static readonly Type TypeOfDbParameter = typeof(DbParameter); - public static readonly Type TypeOfDbParameterCollection = typeof(DbParameterCollection); public static readonly Type TypeOfInt = typeof(int); public static readonly Type TypeOfString = typeof(string); public static readonly Type TypeOfType = typeof(Type); public static readonly Type TypeOfPropertyInfo = typeof(PropertyInfo); public static readonly Type TypeOfBytes = typeof(byte[]); public static readonly Type TypeOfTimeSpan = typeof(TimeSpan); - public static readonly Type TypeOfBindingFlags = typeof(BindingFlags); public static readonly Type TypeOfGuid = typeof(Guid); public static readonly Type TypeOfDateTime = typeof(DateTime); public static readonly Type TypeOfDecimal = typeof(Decimal); @@ -110,6 +107,24 @@ private static class Types public static readonly Type TypeOfShort = typeof(short); public static readonly Type TypeOfBoolean = typeof(bool); public static readonly Type TypeOfConvert = typeof(Convert); + + public static readonly PropertyInfo DbCommandParametersProperty = TypeOfDbCommand.GetProperty("Parameters"); + public static readonly MethodInfo DbCommandCreateParameterMethod = TypeOfDbCommand.GetMethod("CreateParameter"); + public static readonly MethodInfo DbParameterCollectionAddMethod = typeof(DbParameterCollection).GetMethod("Add", new[] { TypeOfObject }); + public static readonly MethodInfo DbParameterCollectionClearMethod = typeof(DbParameterCollection).GetMethod("Clear"); + + // MethodInfo for 'Dynamic|Object' object + public static readonly MethodInfo ObjectGetTypeMethod = TypeOfObject.GetMethod("GetType"); + public static readonly MethodInfo TypeGetPropertyMethod = TypeOfType.GetMethod("GetProperty", new[] { Types.TypeOfString, typeof(BindingFlags) }); + public static readonly MethodInfo PropertyInfoGetValueMethod = TypeOfPropertyInfo.GetMethod("GetValue", new[] { Types.TypeOfObject }); + + public static readonly MethodInfo DbParameterDbTypeSetMethod = TypeOfDbParameter.GetProperty("DbType")?.SetMethod ?? throw new PropertyNotFoundException("DbType"); + public static readonly MethodInfo DbParameterDirectionSetMethod = TypeOfDbParameter.GetProperty("Direction")?.SetMethod ?? throw new PropertyNotFoundException("Direction"); + public static readonly MethodInfo DbParameterSizeSetMethod = TypeOfDbParameter.GetProperty("Size")?.SetMethod ?? throw new PropertyNotFoundException("Size"); + public static readonly MethodInfo DbParameterParameterNameSetMethod = TypeOfDbParameter.GetProperty("ParameterName")?.SetMethod ?? throw new PropertyNotFoundException("ParameterName"); + public static readonly MethodInfo DbParameterPrecisionSetMethod = TypeOfDbParameter.GetProperty("Precision")?.SetMethod ?? throw new PropertyNotFoundException("Precision"); + public static readonly MethodInfo DbParameterScaleSetMethod = TypeOfDbParameter.GetProperty("Scale")?.SetMethod ?? throw new PropertyNotFoundException("Scale"); + public static readonly MethodInfo DbParameterValueSetMethod = TypeOfDbParameter.GetProperty("Value")?.SetMethod ?? throw new PropertyNotFoundException("Value"); } #endregion @@ -129,56 +144,16 @@ public static Action GetDataEntityDbCommandParameterSetterFu IDbSetting dbSetting) where TEntity : class { - // Get the types - var typeOfDbCommand = typeof(DbCommand); - var typeOfEntity = typeof(TEntity); - var typeOfObject = typeof(object); - var typeOfDbParameter = typeof(DbParameter); - var typeOfDbParameterCollection = typeof(DbParameterCollection); - var typeOfInt = typeof(int); - var typeOfString = typeof(string); - var typeOfType = typeof(Type); - var typeOfPropertyInfo = typeof(PropertyInfo); - var typeOfBytes = typeof(byte[]); - var typeOfTimeSpan = typeof(TimeSpan); - var typeOfBindingFlags = typeof(BindingFlags); - var typeOfGuid = typeof(Guid); - var typeOfDateTime = typeof(DateTime); - var typeOfDecimal = typeof(Decimal); - var typeOfFloat = typeof(float); - var typeOfLong = typeof(long); - var typeOfDouble = typeof(Double); - var typeOfShort = typeof(short); - var typeOfBoolean = typeof(bool); - var typeOfConvert = typeof(Convert); - // Variables for arguments - var commandParameterExpression = Expression.Parameter(typeOfDbCommand, "command"); + var typeOfEntity = typeof(TEntity); + var commandParameterExpression = Expression.Parameter(Types.TypeOfDbCommand, "command"); var entityParameterExpression = Expression.Parameter(typeOfEntity, "entity"); // Variables for types var entityProperties = PropertyCache.Get(); - // Variables for DbCommand - var dbCommandParametersProperty = typeOfDbCommand.GetProperty("Parameters"); - var dbCommandCreateParameterMethod = typeOfDbCommand.GetMethod("CreateParameter"); - var dbParameterParameterNameSetMethod = typeOfDbParameter.GetProperty("ParameterName").SetMethod; - var dbParameterValueSetMethod = typeOfDbParameter.GetProperty("Value").SetMethod; - var dbParameterDbTypeSetMethod = typeOfDbParameter.GetProperty("DbType").SetMethod; - var dbParameterDirectionSetMethod = typeOfDbParameter.GetProperty("Direction").SetMethod; - var dbParameterSizeSetMethod = typeOfDbParameter.GetProperty("Size").SetMethod; - var dbParameterPrecisionSetMethod = typeOfDbParameter.GetProperty("Precision").SetMethod; - var dbParameterScaleSetMethod = typeOfDbParameter.GetProperty("Scale").SetMethod; - // Variables for DbParameterCollection - var dbParameterCollection = Expression.Property(commandParameterExpression, dbCommandParametersProperty); - var dbParameterCollectionAddMethod = typeOfDbParameterCollection.GetMethod("Add", new[] { typeOfObject }); - var dbParameterCollectionClearMethod = typeOfDbParameterCollection.GetMethod("Clear"); - - // Variables for 'Dynamic|Object' object - var objectGetTypeMethod = typeOfObject.GetMethod("GetType"); - var typeGetPropertyMethod = typeOfType.GetMethod("GetProperty", new[] { typeOfString, typeOfBindingFlags }); - var propertyInfoGetValueMethod = typeOfPropertyInfo.GetMethod("GetValue", new[] { typeOfObject }); + var dbParameterCollection = Expression.Property(commandParameterExpression, Types.DbCommandParametersProperty); // Other variables var dbTypeResolver = new ClientTypeToDbTypeResolver(); @@ -187,41 +162,37 @@ public static Action GetDataEntityDbCommandParameterSetterFu var propertyVariableList = new List(); var instanceVariable = Expression.Variable(typeOfEntity, "instance"); var instanceType = Expression.Constant(typeOfEntity); - var instanceTypeVariable = Expression.Variable(typeOfType, "instanceType"); + var instanceTypeVariable = Expression.Variable(Types.TypeOfType, "instanceType"); // Input fields properties - if (inputFields?.Any() == true) + inputFields?.ForEach((x, i) => { - for (var index = 0; index < inputFields.Count(); index++) + propertyVariableList.Add(new { - propertyVariableList.Add(new - { - Index = index, - Field = inputFields.ElementAt(index), - Direction = ParameterDirection.Input - }); - } - } + Index = i, + Field = x, + Direction = ParameterDirection.Input + }); + }); // Output fields properties - if (outputFields?.Any() == true) + outputFields?.ForEach(x => { - for (var index = 0; index < outputFields.Count(); index++) + propertyVariableList.Add(new { - propertyVariableList.Add(new - { - Index = inputFields.Count() + index, - Field = outputFields.ElementAt(index), - Direction = ParameterDirection.Output - }); - } - } + Index = propertyVariableList.Count, + Field = x, + Direction = ParameterDirection.Output + }); + }); // Variables for expression body - var bodyExpressions = new List(); + var bodyExpressions = new List + { + Expression.Call(dbParameterCollection, Types.DbParameterCollectionClearMethod) + }; // Clear the parameter collection first - bodyExpressions.Add(Expression.Call(dbParameterCollection, dbParameterCollectionClearMethod)); // Get the current instance var instanceExpressions = new List(); @@ -247,12 +218,12 @@ public static Action GetDataEntityDbCommandParameterSetterFu var propertyName = field.Name.AsUnquoted(true, dbSetting); // Set the proper assignments (property) - if (typeOfEntity == typeOfObject) + if (typeOfEntity == Types.TypeOfObject) { - propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", propertyName)); - propertyInstance = Expression.Call(Expression.Call(instanceVariable, objectGetTypeMethod), - typeGetPropertyMethod, - new[] + propertyVariable = Expression.Variable(Types.TypeOfPropertyInfo, string.Concat("property", propertyName)); + propertyInstance = Expression.Call(Expression.Call(instanceVariable, Types.ObjectGetTypeMethod), + Types.TypeGetPropertyMethod, + new Expression[] { Expression.Constant(propertyName), Expression.Constant(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase) @@ -278,17 +249,7 @@ public static Action GetDataEntityDbCommandParameterSetterFu dbSetting, dbTypeResolver, commandParameterExpression, - dbParameterCollection, - dbCommandCreateParameterMethod, - dbParameterCollectionAddMethod, - dbParameterDbTypeSetMethod, - dbParameterDirectionSetMethod, - dbParameterParameterNameSetMethod, - dbParameterScaleSetMethod, - dbParameterSizeSetMethod, - dbParameterValueSetMethod, - dbParameterPrecisionSetMethod, - propertyInfoGetValueMethod + dbParameterCollection ); // Add the necessary variables @@ -342,62 +303,24 @@ public static Action> GetDataEntitiesDbCommandParamete IEnumerable inputFields, IEnumerable outputFields, int batchSize, - IDbSetting dbSetting) - where TEntity : class + IDbSetting dbSetting) where TEntity : class { // Get the types - var typeOfDbCommand = typeof(DbCommand); var typeOfListEntity = typeof(IList); var typeOfEntity = typeof(TEntity); - var typeOfObject = typeof(object); - var typeOfDbParameter = typeof(DbParameter); - var typeOfDbParameterCollection = typeof(DbParameterCollection); - var typeOfInt = typeof(int); - var typeOfString = typeof(string); - var typeOfType = typeof(Type); - var typeOfPropertyInfo = typeof(PropertyInfo); - var typeOfTimeSpan = typeof(TimeSpan); - var typeOfBindingFlags = typeof(BindingFlags); - var typeOfGuid = typeof(Guid); - var typeOfDateTime = typeof(DateTime); - var typeOfDecimal = typeof(Decimal); - var typeOfFloat = typeof(float); - var typeOfLong = typeof(long); - var typeOfDouble = typeof(Double); - var typeOfShort = typeof(short); - var typeOfBoolean = typeof(bool); - var typeOfConvert = typeof(Convert); // Variables for arguments - var commandParameterExpression = Expression.Parameter(typeOfDbCommand, "command"); + var commandParameterExpression = Expression.Parameter(Types.TypeOfDbCommand, "command"); var entitiesParameterExpression = Expression.Parameter(typeOfListEntity, "entities"); // Variables for types var entityProperties = PropertyCache.Get(); - // Variables for DbCommand - var dbCommandParametersProperty = typeOfDbCommand.GetProperty("Parameters"); - var dbCommandCreateParameterMethod = typeOfDbCommand.GetMethod("CreateParameter"); - var dbParameterParameterNameSetMethod = typeOfDbParameter.GetProperty("ParameterName").SetMethod; - var dbParameterValueSetMethod = typeOfDbParameter.GetProperty("Value").SetMethod; - var dbParameterDbTypeSetMethod = typeOfDbParameter.GetProperty("DbType").SetMethod; - var dbParameterDirectionSetMethod = typeOfDbParameter.GetProperty("Direction").SetMethod; - var dbParameterSizeSetMethod = typeOfDbParameter.GetProperty("Size").SetMethod; - var dbParameterPrecisionSetMethod = typeOfDbParameter.GetProperty("Precision").SetMethod; - var dbParameterScaleSetMethod = typeOfDbParameter.GetProperty("Scale").SetMethod; - // Variables for DbParameterCollection - var dbParameterCollection = Expression.Property(commandParameterExpression, dbCommandParametersProperty); - var dbParameterCollectionAddMethod = typeOfDbParameterCollection.GetMethod("Add", new[] { typeOfObject }); - var dbParameterCollectionClearMethod = typeOfDbParameterCollection.GetMethod("Clear"); - - // Variables for 'Dynamic|Object' object - var objectGetTypeMethod = typeOfObject.GetMethod("GetType"); - var typeGetPropertyMethod = typeOfType.GetMethod("GetProperty", new[] { typeOfString, typeOfBindingFlags }); - var propertyInfoGetValueMethod = typeOfPropertyInfo.GetMethod("GetValue", new[] { typeOfObject }); + var dbParameterCollection = Expression.Property(commandParameterExpression, Types.DbCommandParametersProperty); // Variables for List - var listIndexerMethod = typeOfListEntity.GetMethod("get_Item", new[] { typeOfInt }); + var listIndexerMethod = typeOfListEntity.GetMethod("get_Item", new[] { Types.TypeOfInt }); // Other variables var dbTypeResolver = new ClientTypeToDbTypeResolver(); @@ -406,41 +329,37 @@ public static Action> GetDataEntitiesDbCommandParamete var propertyVariableList = new List(); var instanceVariable = Expression.Variable(typeOfEntity, "instance"); var instanceType = Expression.Constant(typeOfEntity); // Expression.Call(instanceVariable, objectGetTypeMethod); - var instanceTypeVariable = Expression.Variable(typeOfType, "instanceType"); + var instanceTypeVariable = Expression.Variable(Types.TypeOfType, "instanceType"); // Input fields properties - if (inputFields?.Any() == true) + inputFields?.ForEach((x, i) => { - for (var index = 0; index < inputFields.Count(); index++) + propertyVariableList.Add(new { - propertyVariableList.Add(new - { - Index = index, - Field = inputFields.ElementAt(index), - Direction = ParameterDirection.Input - }); - } - } + Index = i, + Field = x, + Direction = ParameterDirection.Input + }); + }); // Output fields properties - if (outputFields?.Any() == true) + outputFields?.ForEach(x => { - for (var index = 0; index < outputFields.Count(); index++) + propertyVariableList.Add(new { - propertyVariableList.Add(new - { - Index = inputFields.Count() + index, - Field = outputFields.ElementAt(index), - Direction = ParameterDirection.Output - }); - } - } + Index = propertyVariableList.Count, + Field = x, + Direction = ParameterDirection.Output + }); + }); // Variables for expression body - var bodyExpressions = new List(); + var bodyExpressions = new List + { + Expression.Call(dbParameterCollection, Types.DbParameterCollectionClearMethod) + }; // Clear the parameter collection first - bodyExpressions.Add(Expression.Call(dbParameterCollection, dbParameterCollectionClearMethod)); // Iterate by batch size for (var entityIndex = 0; entityIndex < batchSize; entityIndex++) @@ -448,10 +367,9 @@ public static Action> GetDataEntitiesDbCommandParamete // Get the current instance var instance = Expression.Call(entitiesParameterExpression, listIndexerMethod, Expression.Constant(entityIndex)); var instanceExpressions = new List(); - var instanceVariables = new List(); + var instanceVariables = new List {instanceVariable}; // Entity instance - instanceVariables.Add(instanceVariable); instanceExpressions.Add(Expression.Assign(instanceVariable, instance)); // Iterate the input fields @@ -471,12 +389,12 @@ public static Action> GetDataEntitiesDbCommandParamete var propertyName = field.Name.AsUnquoted(true, dbSetting); // Set the proper assignments (property) - if (typeOfEntity == typeOfObject) + if (typeOfEntity == Types.TypeOfObject) { - propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", propertyName)); - propertyInstance = Expression.Call(Expression.Call(instanceVariable, objectGetTypeMethod), - typeGetPropertyMethod, - new[] + propertyVariable = Expression.Variable(Types.TypeOfPropertyInfo, string.Concat("property", propertyName)); + propertyInstance = Expression.Call(Expression.Call(instanceVariable, Types.ObjectGetTypeMethod), + Types.TypeGetPropertyMethod, + new Expression[] { Expression.Constant(propertyName), Expression.Constant(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase) @@ -505,17 +423,7 @@ public static Action> GetDataEntitiesDbCommandParamete dbSetting, dbTypeResolver, commandParameterExpression, - dbParameterCollection, - dbCommandCreateParameterMethod, - dbParameterCollectionAddMethod, - dbParameterDbTypeSetMethod, - dbParameterDirectionSetMethod, - dbParameterParameterNameSetMethod, - dbParameterScaleSetMethod, - dbParameterSizeSetMethod, - dbParameterValueSetMethod, - dbParameterPrecisionSetMethod, - propertyInfoGetValueMethod + dbParameterCollection ); // Add the necessary variables @@ -1047,17 +955,7 @@ private static Expression BuildParameterAssignmentExpression IDbSetting dbSetting, ClientTypeToDbTypeResolver dbTypeResolver, Expression commandParameterExpression, - Expression dbParameterCollection, - MethodInfo dbCommandCreateParameterMethod, - MethodInfo dbParameterCollectionAddMethod, - MethodInfo dbParameterDbTypeSetMethod, - MethodInfo dbParameterDirectionSetMethod, - MethodInfo dbParameterParameterNameSetMethod, - MethodInfo dbParameterScaleSetMethod, - MethodInfo dbParameterSizeSetMethod, - MethodInfo dbParameterValueSetMethod, - MethodInfo dbParameterPrecisionSetMethod, - MethodInfo propertyInfoGetValueMethod + Expression dbParameterCollection ) where TEntity : class { // Parameters for the block @@ -1066,11 +964,11 @@ MethodInfo propertyInfoGetValueMethod // Parameter variables var parameterName = dbField.Name.AsUnquoted(true, dbSetting).AsAlphaNumeric(); var parameterVariable = Expression.Variable(Types.TypeOfDbParameter, string.Concat("parameter", parameterName)); - var parameterInstance = Expression.Call(commandParameterExpression, dbCommandCreateParameterMethod); + var parameterInstance = Expression.Call(commandParameterExpression, Types.DbCommandCreateParameterMethod); parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); // Set the name - var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, Expression.Constant(parameterName)); + var nameAssignment = Expression.Call(parameterVariable, Types.DbParameterParameterNameSetMethod, Expression.Constant(parameterName)); parameterAssignments.Add(nameAssignment); // Property instance @@ -1126,7 +1024,7 @@ MethodInfo propertyInfoGetValueMethod // If the property is missing directly, then it could be a dynamic object if (instanceProperty == null) { - value = Expression.Call(property, propertyInfoGetValueMethod, instance); + value = Expression.Call(property, Types.PropertyInfoGetValueMethod, instance); } else { @@ -1268,7 +1166,7 @@ MethodInfo propertyInfoGetValueMethod } // Add to the collection - valueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, valueBlock); + valueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, valueBlock); #endregion @@ -1286,14 +1184,14 @@ MethodInfo propertyInfoGetValueMethod // Set the default type value if (dbField.IsNullable == false && dbField.Type != null) { - dbNullValueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, + dbNullValueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, Expression.Convert(Expression.Default(dbField.Type), Types.TypeOfObject)); } // Set the DBNull value if (dbNullValueAssignment == null) { - dbNullValueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, dbNullValue); + dbNullValueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, dbNullValue); } #endregion @@ -1387,7 +1285,7 @@ MethodInfo propertyInfoGetValueMethod // Set the DB Type if (dbType != null) { - var dbTypeAssignment = Expression.Call(parameterVariable, dbParameterDbTypeSetMethod, Expression.Constant(dbType)); + var dbTypeAssignment = Expression.Call(parameterVariable, Types.DbParameterDbTypeSetMethod, Expression.Constant(dbType)); parameterAssignments.Add(dbTypeAssignment); } //} @@ -1473,7 +1371,7 @@ MethodInfo propertyInfoGetValueMethod if (dbSetting.IsDirectionSupported) { // Set the Parameter Direction - var directionAssignment = Expression.Call(parameterVariable, dbParameterDirectionSetMethod, Expression.Constant(direction)); + var directionAssignment = Expression.Call(parameterVariable, Types.DbParameterDirectionSetMethod, Expression.Constant(direction)); parameterAssignments.Add(directionAssignment); } @@ -1489,7 +1387,7 @@ MethodInfo propertyInfoGetValueMethod // Set the Size if (dbField.Size != null) { - var sizeAssignment = Expression.Call(parameterVariable, dbParameterSizeSetMethod, Expression.Constant(dbField.Size.Value)); + var sizeAssignment = Expression.Call(parameterVariable, Types.DbParameterSizeSetMethod, Expression.Constant(dbField.Size.Value)); parameterAssignments.Add(sizeAssignment); } //} @@ -1501,7 +1399,7 @@ MethodInfo propertyInfoGetValueMethod // Set the Precision if (dbField.Precision != null) { - var precisionAssignment = Expression.Call(parameterVariable, dbParameterPrecisionSetMethod, Expression.Constant(dbField.Precision.Value)); + var precisionAssignment = Expression.Call(parameterVariable, Types.DbParameterPrecisionSetMethod, Expression.Constant(dbField.Precision.Value)); parameterAssignments.Add(precisionAssignment); } @@ -1512,14 +1410,14 @@ MethodInfo propertyInfoGetValueMethod // Set the Scale if (dbField.Scale != null) { - var scaleAssignment = Expression.Call(parameterVariable, dbParameterScaleSetMethod, Expression.Constant(dbField.Scale.Value)); + var scaleAssignment = Expression.Call(parameterVariable, Types.DbParameterScaleSetMethod, Expression.Constant(dbField.Scale.Value)); parameterAssignments.Add(scaleAssignment); } #endregion // Add the actual addition - parameterAssignments.Add(Expression.Call(dbParameterCollection, dbParameterCollectionAddMethod, parameterVariable)); + parameterAssignments.Add(Expression.Call(dbParameterCollection, Types.DbParameterCollectionAddMethod, parameterVariable)); // Return the value return Expression.Block(new[] { parameterVariable }, parameterAssignments); @@ -1537,30 +1435,21 @@ private static Expression BuildParameterAssignmentExpression IDbSetting dbSetting, ClientTypeToDbTypeResolver dbTypeResolver, Expression commandParameterExpression, - Expression dbParameterCollection, - MethodInfo dbCommandCreateParameterMethod, - MethodInfo dbParameterCollectionAddMethod, - MethodInfo dbParameterDbTypeSetMethod, - MethodInfo dbParameterDirectionSetMethod, - MethodInfo dbParameterParameterNameSetMethod, - MethodInfo dbParameterScaleSetMethod, - MethodInfo dbParameterSizeSetMethod, - MethodInfo dbParameterValueSetMethod, - MethodInfo dbParameterPrecisionSetMethod, - MethodInfo propertyInfoGetValueMethod + Expression dbParameterCollection ) where TEntity : class { // Parameters for the block var parameterAssignments = new List(); + var typeOfEntity = typeof(TEntity); // Parameter variables var parameterName = dbField.Name.AsUnquoted(true, dbSetting).AsAlphaNumeric(); var parameterVariable = Expression.Variable(Types.TypeOfDbParameter, string.Concat("parameter", parameterName)); - var parameterInstance = Expression.Call(commandParameterExpression, dbCommandCreateParameterMethod); + var parameterInstance = Expression.Call(commandParameterExpression, Types.DbCommandCreateParameterMethod); parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); // Set the name - var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, + var nameAssignment = Expression.Call(parameterVariable, Types.DbParameterParameterNameSetMethod, Expression.Constant(entityIndex > 0 ? string.Concat(parameterName, "_", entityIndex) : parameterName)); parameterAssignments.Add(nameAssignment); @@ -1582,7 +1471,7 @@ MethodInfo propertyInfoGetValueMethod var valueAssignment = (Expression)null; // Check the proper type of the entity - if (typeof(TEntity) != Types.TypeOfObject && typeof(TEntity).IsGenericType == false) + if (typeOfEntity != Types.TypeOfObject && typeOfEntity.IsGenericType == false) { instanceProperty = classProperty.PropertyInfo; // typeOfEntity.GetProperty(classProperty.PropertyInfo.Name); } @@ -1617,7 +1506,7 @@ MethodInfo propertyInfoGetValueMethod // If the property is missing directly, then it could be a dynamic object if (instanceProperty == null) { - value = Expression.Call(property, propertyInfoGetValueMethod, instance); + value = Expression.Call(property, Types.PropertyInfoGetValueMethod, instance); } else { @@ -1634,7 +1523,7 @@ MethodInfo propertyInfoGetValueMethod // Create a new guid here if (propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */) { - value = Expression.New(Types.TypeOfGuid.GetConstructor(new[] { Types.TypeOfString }), new[] { valueToConvert }); + value = Expression.New(Types.TypeOfGuid.GetConstructor(new[] { Types.TypeOfString }), valueToConvert); } #endregion @@ -1759,12 +1648,12 @@ MethodInfo propertyInfoGetValueMethod } // Add to the collection - valueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, valueBlock); + valueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, valueBlock); #endregion // Check if it is a direct assignment or not - if (typeof(TEntity) != Types.TypeOfObject) + if (typeOfEntity != Types.TypeOfObject) { parameterAssignments.Add(valueAssignment); } @@ -1777,14 +1666,14 @@ MethodInfo propertyInfoGetValueMethod // Set the default type value if (dbField.IsNullable == false && dbField.Type != null) { - dbNullValueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, + dbNullValueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, Expression.Convert(Expression.Default(dbField.Type), Types.TypeOfObject)); } // Set the DBNull value if (dbNullValueAssignment == null) { - dbNullValueAssignment = Expression.Call(parameterVariable, dbParameterValueSetMethod, dbNullValue); + dbNullValueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, dbNullValue); } #endregion @@ -1878,7 +1767,7 @@ MethodInfo propertyInfoGetValueMethod // Set the DB Type if (dbType != null) { - var dbTypeAssignment = Expression.Call(parameterVariable, dbParameterDbTypeSetMethod, Expression.Constant(dbType)); + var dbTypeAssignment = Expression.Call(parameterVariable, Types.DbParameterDbTypeSetMethod, Expression.Constant(dbType)); parameterAssignments.Add(dbTypeAssignment); } //} @@ -1964,7 +1853,7 @@ MethodInfo propertyInfoGetValueMethod if (dbSetting.IsDirectionSupported) { // Set the Parameter Direction - var directionAssignment = Expression.Call(parameterVariable, dbParameterDirectionSetMethod, Expression.Constant(direction)); + var directionAssignment = Expression.Call(parameterVariable, Types.DbParameterDirectionSetMethod, Expression.Constant(direction)); parameterAssignments.Add(directionAssignment); } @@ -1980,7 +1869,7 @@ MethodInfo propertyInfoGetValueMethod // Set the Size if (dbField.Size != null) { - var sizeAssignment = Expression.Call(parameterVariable, dbParameterSizeSetMethod, Expression.Constant(dbField.Size.Value)); + var sizeAssignment = Expression.Call(parameterVariable, Types.DbParameterSizeSetMethod, Expression.Constant(dbField.Size.Value)); parameterAssignments.Add(sizeAssignment); } //} @@ -1992,7 +1881,7 @@ MethodInfo propertyInfoGetValueMethod // Set the Precision if (dbField.Precision != null) { - var precisionAssignment = Expression.Call(parameterVariable, dbParameterPrecisionSetMethod, Expression.Constant(dbField.Precision.Value)); + var precisionAssignment = Expression.Call(parameterVariable, Types.DbParameterPrecisionSetMethod, Expression.Constant(dbField.Precision.Value)); parameterAssignments.Add(precisionAssignment); } @@ -2003,14 +1892,14 @@ MethodInfo propertyInfoGetValueMethod // Set the Scale if (dbField.Scale != null) { - var scaleAssignment = Expression.Call(parameterVariable, dbParameterScaleSetMethod, Expression.Constant(dbField.Scale.Value)); + var scaleAssignment = Expression.Call(parameterVariable, Types.DbParameterScaleSetMethod, Expression.Constant(dbField.Scale.Value)); parameterAssignments.Add(scaleAssignment); } #endregion // Add the actual addition - parameterAssignments.Add(Expression.Call(dbParameterCollection, dbParameterCollectionAddMethod, parameterVariable)); + parameterAssignments.Add(Expression.Call(dbParameterCollection, Types.DbParameterCollectionAddMethod, parameterVariable)); // Return the value return Expression.Block(new[] { parameterVariable }, parameterAssignments); From 8bea5dace4fe1109067a265fcee87536383bf3e6 Mon Sep 17 00:00:00 2001 From: chrissolutions Date: Sun, 19 Apr 2020 17:11:45 +1200 Subject: [PATCH 4/6] Enable ability to convert property to the database type. --- .../RepoDb.IntegrationTests/Helper.cs | 5 +- .../IdentityTableWithDifferentPrimary.cs | 16 +- .../TypeConversionsTest.cs | 6 +- .../Entity/{EntityBase.cs => BaseEntity.cs} | 4 +- .../RepoDb/Reflection/FunctionFactory.cs | 4 +- .../RepoDb/Reflection/FunctionFactory2.cs | 1106 +++++++++++++++-- RepoDb.Core/RepoDb/RepoDb.csproj | 1 - 7 files changed, 1033 insertions(+), 109 deletions(-) rename RepoDb.Core/RepoDb/Entity/{EntityBase.cs => BaseEntity.cs} (90%) diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs index d0e5e57ff..523631460 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using Microsoft.Data.SqlClient; using System.Dynamic; +using System.Globalization; using System.Linq; using System.Reflection; @@ -391,7 +392,7 @@ public static List CreateIdentityTableWithDif { RowGuid = Guid.NewGuid(), ColumnBit = true, - ColumnDateTime = EpocDate.AddDays(index), + ColumnDateTime = EpocDate.AddDays(index).ToString(CultureInfo.InvariantCulture), ColumnDateTime2 = DateTime.UtcNow, ColumnDecimal = index, ColumnFloat = index, @@ -413,7 +414,7 @@ public static IdentityTableWithDifferentPrimary CreateIdentityTableWithDifferent { RowGuid = Guid.NewGuid(), ColumnBit = true, - ColumnDateTime = EpocDate, + ColumnDateTime = EpocDate.ToString(CultureInfo.InvariantCulture), ColumnDateTime2 = DateTime.UtcNow, ColumnDecimal = Convert.ToDecimal(random.Next(int.MinValue, int.MaxValue)), ColumnFloat = Convert.ToSingle(random.Next(int.MinValue, int.MaxValue)), diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Models/IdentityTableWithDifferentPrimary.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Models/IdentityTableWithDifferentPrimary.cs index 68a958758..74ea55e27 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Models/IdentityTableWithDifferentPrimary.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Models/IdentityTableWithDifferentPrimary.cs @@ -1,16 +1,24 @@ -using RepoDb.Attributes; -using System; +using System; +using System.Globalization; +using RepoDb.Attributes; +using RepoDb.Entity; namespace RepoDb.IntegrationTests.Models { [Map("[sc].[IdentityTable]")] - public class IdentityTableWithDifferentPrimary + public class IdentityTableWithDifferentPrimary : BaseEntity { + public IdentityTableWithDifferentPrimary() + { + Map(p => ColumnDateTime).Convert(x => DateTime.Parse(x, CultureInfo.InvariantCulture)); + Map(p => ColumnDateTime).Convert(x => x.ToString(CultureInfo.InvariantCulture)); + } + public long Id { get; set; } [Primary] public Guid RowGuid { get; set; } public bool? ColumnBit { get; set; } - public DateTime? ColumnDateTime { get; set; } + public string ColumnDateTime { get; set; } public DateTime? ColumnDateTime2 { get; set; } public decimal? ColumnDecimal { get; set; } public double? ColumnFloat { get; set; } diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs index 623e0f3cc..b9079435a 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs @@ -392,7 +392,7 @@ public void TestSqlConnectionCrudConvertionFromStringToSmallMoney() #region StringToDateClass [Map("CompleteTable")] - private class StringToDateClass : EntityBase + private class StringToDateClass : BaseEntity { [Primary] public Guid SessionId { get; set; } @@ -432,7 +432,7 @@ public void TestSqlConnectionCrudConversionFromStringToDate() #region StringToDateTimeClass [Map("CompleteTable")] - private class StringToDateTimeClass : EntityBase + private class StringToDateTimeClass : BaseEntity { [Primary] public Guid SessionId { get; set; } @@ -472,7 +472,7 @@ public void TestSqlConnectionCrudConversionFromStringToDateTime() #region StringToDateTime2Class [Map("CompleteTable")] - private class StringToDateTime2Class : EntityBase + private class StringToDateTime2Class : BaseEntity { [Primary] public Guid SessionId { get; set; } diff --git a/RepoDb.Core/RepoDb/Entity/EntityBase.cs b/RepoDb.Core/RepoDb/Entity/BaseEntity.cs similarity index 90% rename from RepoDb.Core/RepoDb/Entity/EntityBase.cs rename to RepoDb.Core/RepoDb/Entity/BaseEntity.cs index 54dd4e242..1058ec112 100644 --- a/RepoDb.Core/RepoDb/Entity/EntityBase.cs +++ b/RepoDb.Core/RepoDb/Entity/BaseEntity.cs @@ -9,14 +9,14 @@ namespace RepoDb.Entity /// /// /// - public abstract class EntityBase where TEntity : class + public abstract class BaseEntity where TEntity : class { /// /// /// public static readonly IPropertyMap PropertyMap = new EntityPropertyMap(); - static EntityBase() + static BaseEntity() { typeof(TEntity).GetProperties().ForEach(x => PropertyMap.Map(x)); } diff --git a/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs index 7d734d926..6d914aae8 100644 --- a/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs +++ b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs @@ -123,7 +123,7 @@ public static Func GetDataReaderToDataEntityConverterFunc }); // Get the member assignments - var memberAssignments = typeof(TEntity).IsSubclassOf(typeof(EntityBase)) + var memberAssignments = typeof(TEntity).IsSubclassOf(typeof(BaseEntity)) ? GetMemberAssignmentsForDataEntity2(newEntityExpression, readerParameterExpression, readerFields, connection) : GetMemberAssignmentsForDataEntity(newEntityExpression, readerParameterExpression, readerFields, connection); @@ -580,7 +580,7 @@ private static IEnumerable GetMemberAssignmentsForDataEntity2< var readerField = dataReaderFields.First(f => string.Equals(f.Name.AsUnquoted(true, dbSetting), mappedName.AsUnquoted(true, dbSetting), StringComparison.OrdinalIgnoreCase)); var propertyType = classProperty.PropertyInfo.PropertyType; var underlyingType = Nullable.GetUnderlyingType(propertyType); - var propertyMap = (EntityPropertyMap)EntityBase.PropertyMap; + var propertyMap = (EntityPropertyMap)BaseEntity.PropertyMap; var converterMethod = (Delegate)((EntityPropertyConverter)propertyMap?.Find(classProperty.PropertyInfo))?.ToProperty; var parameters = converterMethod?.Method.GetParameters(); var targetType = parameters?[0].ParameterType ?? underlyingType ?? propertyType; diff --git a/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs index 5a4b055aa..f589381ee 100644 --- a/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs +++ b/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs @@ -239,18 +239,29 @@ public static Action GetDataEntityDbCommandParameterSetterFu } } - var parameterAssignment = BuildParameterAssignmentExpression( - instanceVariable, - propertyVariable, - field, - classProperty, - (direction == ParameterDirection.Output), - direction, - dbSetting, - dbTypeResolver, - commandParameterExpression, - dbParameterCollection - ); + var parameterAssignment = typeOfEntity.IsSubclassOf(typeof(BaseEntity)) + ? BuildParameterAssignmentExpression2( + instanceVariable, + propertyVariable, + field, + classProperty, + (direction == ParameterDirection.Output), + direction, + dbSetting, + dbTypeResolver, + commandParameterExpression, + dbParameterCollection) + : BuildParameterAssignmentExpression( + instanceVariable, + propertyVariable, + field, + classProperty, + (direction == ParameterDirection.Output), + direction, + dbSetting, + dbTypeResolver, + commandParameterExpression, + dbParameterCollection); // Add the necessary variables if (propertyVariable != null) @@ -412,19 +423,31 @@ public static Action> GetDataEntitiesDbCommandParamete } // Execute the function - var parameterAssignment = BuildParameterAssignmentExpression( - entityIndex /* index */, - instanceVariable /* instance */, - propertyVariable /* property */, - field /* field */, - classProperty /* classProperty */, - (direction == ParameterDirection.Output) /* skipValueAssignment */, - direction /* direction */, - dbSetting, - dbTypeResolver, - commandParameterExpression, - dbParameterCollection - ); + var parameterAssignment = typeOfEntity.IsSubclassOf(typeof(BaseEntity)) + ? BuildParameterAssignmentExpression2( + entityIndex /* index */, + instanceVariable /* instance */, + propertyVariable /* property */, + field /* field */, + classProperty /* classProperty */, + (direction == ParameterDirection.Output) /* skipValueAssignment */, + direction /* direction */, + dbSetting, + dbTypeResolver, + commandParameterExpression, + dbParameterCollection) + : BuildParameterAssignmentExpression( + entityIndex /* index */, + instanceVariable /* instance */, + propertyVariable /* property */, + field /* field */, + classProperty /* classProperty */, + (direction == ParameterDirection.Output) /* skipValueAssignment */, + direction /* direction */, + dbSetting, + dbTypeResolver, + commandParameterExpression, + dbParameterCollection); // Add the necessary variables if (propertyVariable != null) @@ -959,6 +982,7 @@ Expression dbParameterCollection ) where TEntity : class { // Parameters for the block + var typeOfEntity = typeof(TEntity); var parameterAssignments = new List(); // Parameter variables @@ -983,13 +1007,12 @@ Expression dbParameterCollection #region Value // Set the value - if (skipValueAssignment == false) + if (!skipValueAssignment) { // Set the value - var valueAssignment = (Expression)null; // Check the proper type of the entity - if (typeof(TEntity) != Types.TypeOfObject && typeof(TEntity).IsGenericType == false) + if (typeOfEntity != Types.TypeOfObject && typeOfEntity.IsGenericType == false) { instanceProperty = classProperty.PropertyInfo; // typeOfEntity.GetProperty(classProperty.PropertyInfo.Name); } @@ -1166,12 +1189,12 @@ Expression dbParameterCollection } // Add to the collection - valueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, valueBlock); + var valueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, valueBlock); #endregion // Check if it is a direct assignment or not - if (typeof(TEntity) != Types.TypeOfObject) + if (typeOfEntity != Types.TypeOfObject) { parameterAssignments.Add(valueAssignment); } @@ -1423,9 +1446,8 @@ Expression dbParameterCollection return Expression.Block(new[] { parameterVariable }, parameterAssignments); } - private static Expression BuildParameterAssignmentExpression + private static Expression BuildParameterAssignmentExpression2 ( - int entityIndex, Expression instance, Expression property, DbField dbField, @@ -1439,8 +1461,8 @@ Expression dbParameterCollection ) where TEntity : class { // Parameters for the block - var parameterAssignments = new List(); var typeOfEntity = typeof(TEntity); + var parameterAssignments = new List(); // Parameter variables var parameterName = dbField.Name.AsUnquoted(true, dbSetting).AsAlphaNumeric(); @@ -1449,8 +1471,7 @@ Expression dbParameterCollection parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); // Set the name - var nameAssignment = Expression.Call(parameterVariable, Types.DbParameterParameterNameSetMethod, - Expression.Constant(entityIndex > 0 ? string.Concat(parameterName, "_", entityIndex) : parameterName)); + var nameAssignment = Expression.Call(parameterVariable, Types.DbParameterParameterNameSetMethod, Expression.Constant(parameterName)); parameterAssignments.Add(nameAssignment); // Property instance @@ -1459,8 +1480,8 @@ Expression dbParameterCollection var fieldType = dbField.Type?.GetUnderlyingType(); // Property handlers - var handlerInstance = (object)null; - var handlerSetMethod = (MethodInfo)null; + var propertyMap = (EntityPropertyMap)BaseEntity.PropertyMap; + var converterMethod = (Delegate)((EntityPropertyConverter)propertyMap?.Find(classProperty.PropertyInfo))?.ToObject; #region Value @@ -1468,7 +1489,6 @@ Expression dbParameterCollection if (skipValueAssignment == false) { // Set the value - var valueAssignment = (Expression)null; // Check the proper type of the entity if (typeOfEntity != Types.TypeOfObject && typeOfEntity.IsGenericType == false) @@ -1476,32 +1496,10 @@ Expression dbParameterCollection instanceProperty = classProperty.PropertyInfo; // typeOfEntity.GetProperty(classProperty.PropertyInfo.Name); } - #region PropertyHandler - - var propertyHandlerAttribute = instanceProperty?.GetCustomAttribute(); - - if (propertyHandlerAttribute != null) - { - // Get from the attribute - handlerInstance = PropertyHandlerCache.Get(classProperty.PropertyInfo); - handlerSetMethod = propertyHandlerAttribute.HandlerType.GetMethod("Set"); - } - else - { - // Get from the type level mappings (DB type) - handlerInstance = PropertyHandlerMapper.Get(dbField.Type.GetUnderlyingType()); - if (handlerInstance != null) - { - handlerSetMethod = handlerInstance.GetType().GetMethod("Set"); - } - } - - #endregion - #region Instance.Property or PropertyInfo.GetValue() // Set the value - var value = (Expression)null; + Expression value; // If the property is missing directly, then it could be a dynamic object if (instanceProperty == null) @@ -1510,9 +1508,9 @@ Expression dbParameterCollection } else { - propertyType = instanceProperty?.PropertyType.GetUnderlyingType(); + propertyType = instanceProperty.PropertyType.GetUnderlyingType(); - if (handlerInstance == null) + if (converterMethod == null) { if (Converter.ConversionType == ConversionType.Automatic) { @@ -1523,7 +1521,7 @@ Expression dbParameterCollection // Create a new guid here if (propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */) { - value = Expression.New(Types.TypeOfGuid.GetConstructor(new[] { Types.TypeOfString }), valueToConvert); + value = Expression.New(Types.TypeOfGuid.GetConstructor(new[] { Types.TypeOfString }), new[] { valueToConvert }); } #endregion @@ -1578,10 +1576,10 @@ Expression dbParameterCollection } else { - var converterMethod = typeof(EnumHelper).GetMethod("Convert"); - if (converterMethod != null) + var convertMethod = typeof(EnumHelper).GetMethod("Convert"); + if (convertMethod != null) { - value = Expression.Call(converterMethod, + value = Expression.Call(convertMethod, Expression.Constant(instanceProperty.PropertyType), Expression.Constant(dbField.Type), Expression.Convert(value, Types.TypeOfObject), @@ -1596,19 +1594,7 @@ Expression dbParameterCollection { // Get the value directly from the property value = Expression.Property(instance, instanceProperty); - - #region PropertyHandler - - if (handlerInstance != null) - { - var setParameter = handlerSetMethod.GetParameters().First(); - value = Expression.Call(Expression.Constant(handlerInstance), - handlerSetMethod, - Expression.Convert(value, setParameter.ParameterType), - Expression.Constant(classProperty)); - } - - #endregion + value = Expression.Call(Expression.Constant(converterMethod.Target), converterMethod.Method, value); } // Convert to object @@ -1616,28 +1602,23 @@ Expression dbParameterCollection } // Declare the variable for the value assignment - var valueBlock = (Expression)null; - var isNullable = dbField.IsNullable == true || - instanceProperty == null || - ( - instanceProperty != null && - ( - instanceProperty.PropertyType.IsValueType == false || - Nullable.GetUnderlyingType(instanceProperty.PropertyType) != null - ) - ); + Expression valueBlock; + var isNullable = dbField.IsNullable + || instanceProperty == null + || instanceProperty.PropertyType.IsValueType == false + || Nullable.GetUnderlyingType(instanceProperty.PropertyType) != null; // The value for DBNull.Value var dbNullValue = Expression.Convert(Expression.Constant(DBNull.Value), Types.TypeOfObject); // Check if the property is nullable - if (isNullable == true) + if (isNullable) { // Identification of the DBNull var valueVariable = Expression.Variable(Types.TypeOfObject, string.Concat("valueOf", parameterName)); var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); - // Set the propert value + // Set the property value valueBlock = Expression.Block(new[] { valueVariable }, Expression.Assign(valueVariable, value), Expression.Condition(valueIsNull, dbNullValue, valueVariable)); @@ -1648,12 +1629,12 @@ Expression dbParameterCollection } // Add to the collection - valueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, valueBlock); + var valueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, valueBlock); #endregion // Check if it is a direct assignment or not - if (typeOfEntity != Types.TypeOfObject) + if (typeof(TEntity) != Types.TypeOfObject) { parameterAssignments.Add(valueAssignment); } @@ -1735,7 +1716,7 @@ Expression dbParameterCollection } // Get the class property - if (dbType == null && handlerInstance == null) + if (dbType == null && converterMethod == null) { if (fieldOrPropertyType != typeof(SqlVariant) && !string.Equals(dbField.DatabaseType, "sql_variant", StringComparison.OrdinalIgnoreCase)) { @@ -1904,5 +1885,940 @@ Expression dbParameterCollection // Return the value return Expression.Block(new[] { parameterVariable }, parameterAssignments); } + + private static Expression BuildParameterAssignmentExpression + ( + int entityIndex, + Expression instance, + Expression property, + DbField dbField, + ClassProperty classProperty, + bool skipValueAssignment, + ParameterDirection direction, + IDbSetting dbSetting, + ClientTypeToDbTypeResolver dbTypeResolver, + Expression commandParameterExpression, + Expression dbParameterCollection + ) where TEntity : class + { + // Parameters for the block + var parameterAssignments = new List(); + var typeOfEntity = typeof(TEntity); + + // Parameter variables + var parameterName = dbField.Name.AsUnquoted(true, dbSetting).AsAlphaNumeric(); + var parameterVariable = Expression.Variable(Types.TypeOfDbParameter, string.Concat("parameter", parameterName)); + var parameterInstance = Expression.Call(commandParameterExpression, Types.DbCommandCreateParameterMethod); + parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); + + // Set the name + var nameAssignment = Expression.Call(parameterVariable, Types.DbParameterParameterNameSetMethod, + Expression.Constant(entityIndex > 0 ? string.Concat(parameterName, "_", entityIndex) : parameterName)); + parameterAssignments.Add(nameAssignment); + + // Property instance + var instanceProperty = (PropertyInfo)null; + var propertyType = (Type)null; + var fieldType = dbField.Type?.GetUnderlyingType(); + + // Property handlers + var handlerInstance = (object)null; + var handlerSetMethod = (MethodInfo)null; + + #region Value + + // Set the value + if (skipValueAssignment == false) + { + // Set the value + var valueAssignment = (Expression)null; + + // Check the proper type of the entity + if (typeOfEntity != Types.TypeOfObject && typeOfEntity.IsGenericType == false) + { + instanceProperty = classProperty.PropertyInfo; // typeOfEntity.GetProperty(classProperty.PropertyInfo.Name); + } + + #region PropertyHandler + + var propertyHandlerAttribute = instanceProperty?.GetCustomAttribute(); + + if (propertyHandlerAttribute != null) + { + // Get from the attribute + handlerInstance = PropertyHandlerCache.Get(classProperty.PropertyInfo); + handlerSetMethod = propertyHandlerAttribute.HandlerType.GetMethod("Set"); + } + else + { + // Get from the type level mappings (DB type) + handlerInstance = PropertyHandlerMapper.Get(dbField.Type.GetUnderlyingType()); + if (handlerInstance != null) + { + handlerSetMethod = handlerInstance.GetType().GetMethod("Set"); + } + } + + #endregion + + #region Instance.Property or PropertyInfo.GetValue() + + // Set the value + var value = (Expression)null; + + // If the property is missing directly, then it could be a dynamic object + if (instanceProperty == null) + { + value = Expression.Call(property, Types.PropertyInfoGetValueMethod, instance); + } + else + { + propertyType = instanceProperty?.PropertyType.GetUnderlyingType(); + + if (handlerInstance == null) + { + if (Converter.ConversionType == ConversionType.Automatic) + { + var valueToConvert = Expression.Property(instance, instanceProperty); + + #region StringToGuid + + // Create a new guid here + if (propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */) + { + value = Expression.New(Types.TypeOfGuid.GetConstructor(new[] { Types.TypeOfString }), valueToConvert); + } + + #endregion + + #region GuidToString + + // Call the System.Convert conversion + else if (propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString/* GuidToString*/) + { + var convertMethod = typeof(Convert).GetMethod("ToString", new[] { Types.TypeOfObject }); + value = Expression.Call(convertMethod, Expression.Convert(valueToConvert, Types.TypeOfObject)); + value = Expression.Convert(value, fieldType); + } + + #endregion + + else + { + value = valueToConvert; + } + } + else + { + // Get the Class.Property + value = Expression.Property(instance, instanceProperty); + } + + #region EnumAsIntForString + + if (propertyType.IsEnum) + { + var convertToTypeMethod = (MethodInfo)null; + if (convertToTypeMethod == null) + { + var mappedToType = classProperty?.GetDbType(); + if (mappedToType == null) + { + mappedToType = new ClientTypeToDbTypeResolver().Resolve(dbField.Type); + } + if (mappedToType != null) + { + convertToTypeMethod = Types.TypeOfConvert.GetMethod(string.Concat("To", mappedToType.ToString()), new[] { Types.TypeOfObject }); + } + } + if (convertToTypeMethod == null) + { + convertToTypeMethod = Types.TypeOfConvert.GetMethod(string.Concat("To", dbField.Type.Name), new[] { Types.TypeOfObject }); + } + if (convertToTypeMethod == null) + { + throw new ConverterNotFoundException($"The convert between '{propertyType.FullName}' and database type '{dbField.DatabaseType}' (of .NET CLR '{dbField.Type.FullName}') is not found."); + } + else + { + var converterMethod = typeof(EnumHelper).GetMethod("Convert"); + if (converterMethod != null) + { + value = Expression.Call(converterMethod, + Expression.Constant(instanceProperty.PropertyType), + Expression.Constant(dbField.Type), + Expression.Convert(value, Types.TypeOfObject), + Expression.Constant(convertToTypeMethod)); + } + } + } + + #endregion + } + else + { + // Get the value directly from the property + value = Expression.Property(instance, instanceProperty); + + #region PropertyHandler + + if (handlerInstance != null) + { + var setParameter = handlerSetMethod.GetParameters().First(); + value = Expression.Call(Expression.Constant(handlerInstance), + handlerSetMethod, + Expression.Convert(value, setParameter.ParameterType), + Expression.Constant(classProperty)); + } + + #endregion + } + + // Convert to object + value = Expression.Convert(value, Types.TypeOfObject); + } + + // Declare the variable for the value assignment + var valueBlock = (Expression)null; + var isNullable = dbField.IsNullable == true || + instanceProperty == null || + ( + instanceProperty != null && + ( + instanceProperty.PropertyType.IsValueType == false || + Nullable.GetUnderlyingType(instanceProperty.PropertyType) != null + ) + ); + + // The value for DBNull.Value + var dbNullValue = Expression.Convert(Expression.Constant(DBNull.Value), Types.TypeOfObject); + + // Check if the property is nullable + if (isNullable == true) + { + // Identification of the DBNull + var valueVariable = Expression.Variable(Types.TypeOfObject, string.Concat("valueOf", parameterName)); + var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); + + // Set the propert value + valueBlock = Expression.Block(new[] { valueVariable }, + Expression.Assign(valueVariable, value), + Expression.Condition(valueIsNull, dbNullValue, valueVariable)); + } + else + { + valueBlock = value; + } + + // Add to the collection + valueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, valueBlock); + + #endregion + + // Check if it is a direct assignment or not + if (typeOfEntity != Types.TypeOfObject) + { + parameterAssignments.Add(valueAssignment); + } + else + { + var dbNullValueAssignment = (Expression)null; + + #region DBNull.Value + + // Set the default type value + if (dbField.IsNullable == false && dbField.Type != null) + { + dbNullValueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, + Expression.Convert(Expression.Default(dbField.Type), Types.TypeOfObject)); + } + + // Set the DBNull value + if (dbNullValueAssignment == null) + { + dbNullValueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, dbNullValue); + } + + #endregion + + // Check the presence of the property + var propertyIsNull = Expression.Equal(property, Expression.Constant(null)); + + // Add to parameter assignment + parameterAssignments.Add(Expression.Condition(propertyIsNull, dbNullValueAssignment, valueAssignment)); + } + } + + #endregion + + #region DbType + + #region DbType + + // Set for non Timestamp, not-working in System.Data.SqlClient but is working at Microsoft.Data.SqlClient + // It is actually me who file this issue to Microsoft :) + //if (fieldOrPropertyType != typeOfTimeSpan) + //{ + // Identify the DB Type + var fieldOrPropertyType = (Type)null; + var dbType = (DbType?)null; + + // Identify the conversion + if (Converter.ConversionType == ConversionType.Automatic) + { + // Identity the conversion + if (propertyType == Types.TypeOfDateTime && fieldType == Types.TypeOfString /* DateTimeToString */ || + propertyType == Types.TypeOfDecimal && (fieldType == Types.TypeOfFloat || fieldType == Types.TypeOfDouble) /* DecimalToFloat/DecimalToDouble */ || + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfLong /* DoubleToBigint */|| + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfInt /* DoubleToBigint */ || + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfShort /* DoubleToShort */|| + propertyType == Types.TypeOfFloat && fieldType == Types.TypeOfLong /* FloatToBigint */ || + propertyType == Types.TypeOfFloat && fieldType == Types.TypeOfShort /* FloatToShort */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDateTime /* StringToDate */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfShort /* StringToShort */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfInt /* StringToInt */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfLong /* StringToLong */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDouble /* StringToDouble */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDecimal /* StringToDecimal */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfFloat /* StringToFloat */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfBoolean /* StringToBoolean */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */ || + propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString /* GuidToString */) + { + fieldOrPropertyType = fieldType; + } + else if (propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString /* UniqueIdentifierToString */) + { + fieldOrPropertyType = propertyType; + } + if (fieldOrPropertyType != null) + { + dbType = dbTypeResolver.Resolve(fieldOrPropertyType); + } + } + + // Get the class property + if (dbType == null && handlerInstance == null) + { + if (fieldOrPropertyType != typeof(SqlVariant) && !string.Equals(dbField.DatabaseType, "sql_variant", StringComparison.OrdinalIgnoreCase)) + { + dbType = classProperty?.GetDbType(); + } + } + + // Set to normal if null + if (fieldOrPropertyType == null) + { + fieldOrPropertyType = dbField.Type?.GetUnderlyingType() ?? instanceProperty?.PropertyType.GetUnderlyingType(); + } + + if (fieldOrPropertyType != null) + { + // Get the type mapping + if (dbType == null) + { + dbType = TypeMapper.Get(fieldOrPropertyType); + } + + // Use the resolver + if (dbType == null) + { + dbType = dbTypeResolver.Resolve(fieldOrPropertyType); + } + } + + // Set the DB Type + if (dbType != null) + { + var dbTypeAssignment = Expression.Call(parameterVariable, Types.DbParameterDbTypeSetMethod, Expression.Constant(dbType)); + parameterAssignments.Add(dbTypeAssignment); + } + //} + + #endregion + + #region SqlDbType (System) + + // Get the SqlDbType value from SystemSqlServerTypeMapAttribute + var systemSqlServerTypeMapAttribute = GetSystemSqlServerTypeMapAttribute(classProperty); + if (systemSqlServerTypeMapAttribute != null) + { + var systemSqlDbTypeValue = GetSystemSqlServerDbTypeFromAttribute(systemSqlServerTypeMapAttribute); + var systemSqlParameterType = GetSystemSqlServerParameterTypeFromAttribute(systemSqlServerTypeMapAttribute); + var dbParameterSystemSqlDbTypeSetMethod = GetSystemSqlServerDbTypeFromAttributeSetMethod(systemSqlServerTypeMapAttribute); + var systemSqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, systemSqlParameterType), + dbParameterSystemSqlDbTypeSetMethod, + Expression.Constant(systemSqlDbTypeValue)); + parameterAssignments.Add(systemSqlDbTypeAssignment); + } + + #endregion + + #region SqlDbType (Microsoft) + + // Get the SqlDbType value from MicrosoftSqlServerTypeMapAttribute + var microsoftSqlServerTypeMapAttribute = GetMicrosoftSqlServerTypeMapAttribute(classProperty); + if (microsoftSqlServerTypeMapAttribute != null) + { + var microsoftSqlDbTypeValue = GetMicrosoftSqlServerDbTypeFromAttribute(microsoftSqlServerTypeMapAttribute); + var microsoftSqlParameterType = GetMicrosoftSqlServerParameterTypeFromAttribute(microsoftSqlServerTypeMapAttribute); + var dbParameterMicrosoftSqlDbTypeSetMethod = GetMicrosoftSqlServerDbTypeFromAttributeSetMethod(microsoftSqlServerTypeMapAttribute); + var microsoftSqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, microsoftSqlParameterType), + dbParameterMicrosoftSqlDbTypeSetMethod, + Expression.Constant(microsoftSqlDbTypeValue)); + parameterAssignments.Add(microsoftSqlDbTypeAssignment); + } + + #endregion + + #region MySqlDbType + + // Get the MySqlDbType value from MySqlDbTypeAttribute + var mysqlDbTypeTypeMapAttribute = GetMySqlDbTypeTypeMapAttribute(classProperty); + if (mysqlDbTypeTypeMapAttribute != null) + { + var mySqlDbTypeValue = GetMySqlDbTypeFromAttribute(mysqlDbTypeTypeMapAttribute); + var mySqlParameterType = GetMySqlParameterTypeFromAttribute(mysqlDbTypeTypeMapAttribute); + var dbParameterMySqlDbTypeSetMethod = GetMySqlDbTypeFromAttributeSetMethod(mysqlDbTypeTypeMapAttribute); + var mySqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, mySqlParameterType), + dbParameterMySqlDbTypeSetMethod, + Expression.Constant(mySqlDbTypeValue)); + parameterAssignments.Add(mySqlDbTypeAssignment); + } + + #endregion + + #region NpgsqlDbType + + // Get the NpgsqlDbType value from NpgsqlTypeMapAttribute + var npgsqlDbTypeTypeMapAttribute = GetNpgsqlDbTypeTypeMapAttribute(classProperty); + if (npgsqlDbTypeTypeMapAttribute != null) + { + var npgsqlDbTypeValue = GetNpgsqlDbTypeFromAttribute(npgsqlDbTypeTypeMapAttribute); + var npgsqlParameterType = GetNpgsqlParameterTypeFromAttribute(npgsqlDbTypeTypeMapAttribute); + var dbParameterNpgsqlDbTypeSetMethod = GetNpgsqlDbTypeFromAttributeSetMethod(npgsqlDbTypeTypeMapAttribute); + var npgsqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, npgsqlParameterType), + dbParameterNpgsqlDbTypeSetMethod, + Expression.Constant(npgsqlDbTypeValue)); + parameterAssignments.Add(npgsqlDbTypeAssignment); + } + + #endregion + + #endregion + + #region Direction + + if (dbSetting.IsDirectionSupported) + { + // Set the Parameter Direction + var directionAssignment = Expression.Call(parameterVariable, Types.DbParameterDirectionSetMethod, Expression.Constant(direction)); + parameterAssignments.Add(directionAssignment); + } + + #endregion + + #region Size + + // Set only for non-image + // By default, SQL Server only put (16 size), and that would fail if the user + // used this type for their binary columns and assign a much longer values + //if (!string.Equals(field.DatabaseType, "image", StringComparison.OrdinalIgnoreCase)) + //{ + // Set the Size + if (dbField.Size != null) + { + var sizeAssignment = Expression.Call(parameterVariable, Types.DbParameterSizeSetMethod, Expression.Constant(dbField.Size.Value)); + parameterAssignments.Add(sizeAssignment); + } + //} + + #endregion + + #region Precision + + // Set the Precision + if (dbField.Precision != null) + { + var precisionAssignment = Expression.Call(parameterVariable, Types.DbParameterPrecisionSetMethod, Expression.Constant(dbField.Precision.Value)); + parameterAssignments.Add(precisionAssignment); + } + + #endregion + + #region Scale + + // Set the Scale + if (dbField.Scale != null) + { + var scaleAssignment = Expression.Call(parameterVariable, Types.DbParameterScaleSetMethod, Expression.Constant(dbField.Scale.Value)); + parameterAssignments.Add(scaleAssignment); + } + + #endregion + + // Add the actual addition + parameterAssignments.Add(Expression.Call(dbParameterCollection, Types.DbParameterCollectionAddMethod, parameterVariable)); + + // Return the value + return Expression.Block(new[] { parameterVariable }, parameterAssignments); + } + + private static Expression BuildParameterAssignmentExpression2 + ( + int entityIndex, + Expression instance, + Expression property, + DbField dbField, + ClassProperty classProperty, + bool skipValueAssignment, + ParameterDirection direction, + IDbSetting dbSetting, + ClientTypeToDbTypeResolver dbTypeResolver, + Expression commandParameterExpression, + Expression dbParameterCollection + ) where TEntity : class + { + // Parameters for the block + var parameterAssignments = new List(); + var typeOfEntity = typeof(TEntity); + + // Parameter variables + var parameterName = dbField.Name.AsUnquoted(true, dbSetting).AsAlphaNumeric(); + var parameterVariable = Expression.Variable(Types.TypeOfDbParameter, string.Concat("parameter", parameterName)); + var parameterInstance = Expression.Call(commandParameterExpression, Types.DbCommandCreateParameterMethod); + parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); + + // Set the name + var nameAssignment = Expression.Call(parameterVariable, Types.DbParameterParameterNameSetMethod, + Expression.Constant(entityIndex > 0 ? string.Concat(parameterName, "_", entityIndex) : parameterName)); + parameterAssignments.Add(nameAssignment); + + // Property instance + var instanceProperty = (PropertyInfo)null; + var propertyType = (Type)null; + var fieldType = dbField.Type?.GetUnderlyingType(); + + // Property handlers + var propertyMap = (EntityPropertyMap)BaseEntity.PropertyMap; + var converterMethod = (Delegate)((EntityPropertyConverter)propertyMap?.Find(classProperty.PropertyInfo))?.ToObject; + + + #region Value + + // Set the value + if (skipValueAssignment == false) + { + // Set the value + var valueAssignment = (Expression)null; + + // Check the proper type of the entity + if (typeOfEntity != Types.TypeOfObject && typeOfEntity.IsGenericType == false) + { + instanceProperty = classProperty.PropertyInfo; // typeOfEntity.GetProperty(classProperty.PropertyInfo.Name); + } + + #region Instance.Property or PropertyInfo.GetValue() + + // Set the value + var value = (Expression)null; + + // If the property is missing directly, then it could be a dynamic object + if (instanceProperty == null) + { + value = Expression.Call(property, Types.PropertyInfoGetValueMethod, instance); + } + else + { + propertyType = instanceProperty?.PropertyType.GetUnderlyingType(); + + if (converterMethod == null) + { + if (Converter.ConversionType == ConversionType.Automatic) + { + var valueToConvert = Expression.Property(instance, instanceProperty); + + #region StringToGuid + + // Create a new guid here + if (propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */) + { + value = Expression.New(Types.TypeOfGuid.GetConstructor(new[] { Types.TypeOfString }), valueToConvert); + } + + #endregion + + #region GuidToString + + // Call the System.Convert conversion + else if (propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString/* GuidToString*/) + { + var convertMethod = typeof(Convert).GetMethod("ToString", new[] { Types.TypeOfObject }); + value = Expression.Call(convertMethod, Expression.Convert(valueToConvert, Types.TypeOfObject)); + value = Expression.Convert(value, fieldType); + } + + #endregion + + else + { + value = valueToConvert; + } + } + else + { + // Get the Class.Property + value = Expression.Property(instance, instanceProperty); + } + + #region EnumAsIntForString + + if (propertyType.IsEnum) + { + var convertToTypeMethod = (MethodInfo)null; + if (convertToTypeMethod == null) + { + var mappedToType = classProperty?.GetDbType(); + if (mappedToType == null) + { + mappedToType = new ClientTypeToDbTypeResolver().Resolve(dbField.Type); + } + if (mappedToType != null) + { + convertToTypeMethod = Types.TypeOfConvert.GetMethod(string.Concat("To", mappedToType.ToString()), new[] { Types.TypeOfObject }); + } + } + if (convertToTypeMethod == null) + { + convertToTypeMethod = Types.TypeOfConvert.GetMethod(string.Concat("To", dbField.Type.Name), new[] { Types.TypeOfObject }); + } + if (convertToTypeMethod == null) + { + throw new ConverterNotFoundException($"The convert between '{propertyType.FullName}' and database type '{dbField.DatabaseType}' (of .NET CLR '{dbField.Type.FullName}') is not found."); + } + else + { + var convertMethod = typeof(EnumHelper).GetMethod("Convert"); + if (convertMethod != null) + { + value = Expression.Call(convertMethod, + Expression.Constant(instanceProperty.PropertyType), + Expression.Constant(dbField.Type), + Expression.Convert(value, Types.TypeOfObject), + Expression.Constant(convertToTypeMethod)); + } + } + } + + #endregion + } + else + { + // Get the value directly from the property + value = Expression.Property(instance, instanceProperty); + + // Get the value directly from the property + value = Expression.Property(instance, instanceProperty); + value = Expression.Call(Expression.Constant(converterMethod.Target), converterMethod.Method, value); + } + + // Convert to object + value = Expression.Convert(value, Types.TypeOfObject); + } + + // Declare the variable for the value assignment + var valueBlock = (Expression)null; + var isNullable = dbField.IsNullable == true || + instanceProperty == null || + ( + instanceProperty != null && + ( + instanceProperty.PropertyType.IsValueType == false || + Nullable.GetUnderlyingType(instanceProperty.PropertyType) != null + ) + ); + + // The value for DBNull.Value + var dbNullValue = Expression.Convert(Expression.Constant(DBNull.Value), Types.TypeOfObject); + + // Check if the property is nullable + if (isNullable == true) + { + // Identification of the DBNull + var valueVariable = Expression.Variable(Types.TypeOfObject, string.Concat("valueOf", parameterName)); + var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); + + // Set the propert value + valueBlock = Expression.Block(new[] { valueVariable }, + Expression.Assign(valueVariable, value), + Expression.Condition(valueIsNull, dbNullValue, valueVariable)); + } + else + { + valueBlock = value; + } + + // Add to the collection + valueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, valueBlock); + + #endregion + + // Check if it is a direct assignment or not + if (typeOfEntity != Types.TypeOfObject) + { + parameterAssignments.Add(valueAssignment); + } + else + { + var dbNullValueAssignment = (Expression)null; + + #region DBNull.Value + + // Set the default type value + if (dbField.IsNullable == false && dbField.Type != null) + { + dbNullValueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, + Expression.Convert(Expression.Default(dbField.Type), Types.TypeOfObject)); + } + + // Set the DBNull value + if (dbNullValueAssignment == null) + { + dbNullValueAssignment = Expression.Call(parameterVariable, Types.DbParameterValueSetMethod, dbNullValue); + } + + #endregion + + // Check the presence of the property + var propertyIsNull = Expression.Equal(property, Expression.Constant(null)); + + // Add to parameter assignment + parameterAssignments.Add(Expression.Condition(propertyIsNull, dbNullValueAssignment, valueAssignment)); + } + } + + #endregion + + #region DbType + + #region DbType + + // Set for non Timestamp, not-working in System.Data.SqlClient but is working at Microsoft.Data.SqlClient + // It is actually me who file this issue to Microsoft :) + //if (fieldOrPropertyType != typeOfTimeSpan) + //{ + // Identify the DB Type + var fieldOrPropertyType = (Type)null; + var dbType = (DbType?)null; + + // Identify the conversion + if (Converter.ConversionType == ConversionType.Automatic) + { + // Identity the conversion + if (propertyType == Types.TypeOfDateTime && fieldType == Types.TypeOfString /* DateTimeToString */ || + propertyType == Types.TypeOfDecimal && (fieldType == Types.TypeOfFloat || fieldType == Types.TypeOfDouble) /* DecimalToFloat/DecimalToDouble */ || + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfLong /* DoubleToBigint */|| + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfInt /* DoubleToBigint */ || + propertyType == Types.TypeOfDouble && fieldType == Types.TypeOfShort /* DoubleToShort */|| + propertyType == Types.TypeOfFloat && fieldType == Types.TypeOfLong /* FloatToBigint */ || + propertyType == Types.TypeOfFloat && fieldType == Types.TypeOfShort /* FloatToShort */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDateTime /* StringToDate */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfShort /* StringToShort */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfInt /* StringToInt */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfLong /* StringToLong */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDouble /* StringToDouble */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfDecimal /* StringToDecimal */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfFloat /* StringToFloat */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfBoolean /* StringToBoolean */ || + propertyType == Types.TypeOfString && fieldType == Types.TypeOfGuid /* StringToGuid */ || + propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString /* GuidToString */) + { + fieldOrPropertyType = fieldType; + } + else if (propertyType == Types.TypeOfGuid && fieldType == Types.TypeOfString /* UniqueIdentifierToString */) + { + fieldOrPropertyType = propertyType; + } + if (fieldOrPropertyType != null) + { + dbType = dbTypeResolver.Resolve(fieldOrPropertyType); + } + } + + // Get the class property + if (dbType == null && converterMethod == null) + { + if (fieldOrPropertyType != typeof(SqlVariant) && !string.Equals(dbField.DatabaseType, "sql_variant", StringComparison.OrdinalIgnoreCase)) + { + dbType = classProperty?.GetDbType(); + } + } + + // Set to normal if null + if (fieldOrPropertyType == null) + { + fieldOrPropertyType = dbField.Type?.GetUnderlyingType() ?? instanceProperty?.PropertyType.GetUnderlyingType(); + } + + if (fieldOrPropertyType != null) + { + // Get the type mapping + if (dbType == null) + { + dbType = TypeMapper.Get(fieldOrPropertyType); + } + + // Use the resolver + if (dbType == null) + { + dbType = dbTypeResolver.Resolve(fieldOrPropertyType); + } + } + + // Set the DB Type + if (dbType != null) + { + var dbTypeAssignment = Expression.Call(parameterVariable, Types.DbParameterDbTypeSetMethod, Expression.Constant(dbType)); + parameterAssignments.Add(dbTypeAssignment); + } + //} + + #endregion + + #region SqlDbType (System) + + // Get the SqlDbType value from SystemSqlServerTypeMapAttribute + var systemSqlServerTypeMapAttribute = GetSystemSqlServerTypeMapAttribute(classProperty); + if (systemSqlServerTypeMapAttribute != null) + { + var systemSqlDbTypeValue = GetSystemSqlServerDbTypeFromAttribute(systemSqlServerTypeMapAttribute); + var systemSqlParameterType = GetSystemSqlServerParameterTypeFromAttribute(systemSqlServerTypeMapAttribute); + var dbParameterSystemSqlDbTypeSetMethod = GetSystemSqlServerDbTypeFromAttributeSetMethod(systemSqlServerTypeMapAttribute); + var systemSqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, systemSqlParameterType), + dbParameterSystemSqlDbTypeSetMethod, + Expression.Constant(systemSqlDbTypeValue)); + parameterAssignments.Add(systemSqlDbTypeAssignment); + } + + #endregion + + #region SqlDbType (Microsoft) + + // Get the SqlDbType value from MicrosoftSqlServerTypeMapAttribute + var microsoftSqlServerTypeMapAttribute = GetMicrosoftSqlServerTypeMapAttribute(classProperty); + if (microsoftSqlServerTypeMapAttribute != null) + { + var microsoftSqlDbTypeValue = GetMicrosoftSqlServerDbTypeFromAttribute(microsoftSqlServerTypeMapAttribute); + var microsoftSqlParameterType = GetMicrosoftSqlServerParameterTypeFromAttribute(microsoftSqlServerTypeMapAttribute); + var dbParameterMicrosoftSqlDbTypeSetMethod = GetMicrosoftSqlServerDbTypeFromAttributeSetMethod(microsoftSqlServerTypeMapAttribute); + var microsoftSqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, microsoftSqlParameterType), + dbParameterMicrosoftSqlDbTypeSetMethod, + Expression.Constant(microsoftSqlDbTypeValue)); + parameterAssignments.Add(microsoftSqlDbTypeAssignment); + } + + #endregion + + #region MySqlDbType + + // Get the MySqlDbType value from MySqlDbTypeAttribute + var mysqlDbTypeTypeMapAttribute = GetMySqlDbTypeTypeMapAttribute(classProperty); + if (mysqlDbTypeTypeMapAttribute != null) + { + var mySqlDbTypeValue = GetMySqlDbTypeFromAttribute(mysqlDbTypeTypeMapAttribute); + var mySqlParameterType = GetMySqlParameterTypeFromAttribute(mysqlDbTypeTypeMapAttribute); + var dbParameterMySqlDbTypeSetMethod = GetMySqlDbTypeFromAttributeSetMethod(mysqlDbTypeTypeMapAttribute); + var mySqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, mySqlParameterType), + dbParameterMySqlDbTypeSetMethod, + Expression.Constant(mySqlDbTypeValue)); + parameterAssignments.Add(mySqlDbTypeAssignment); + } + + #endregion + + #region NpgsqlDbType + + // Get the NpgsqlDbType value from NpgsqlTypeMapAttribute + var npgsqlDbTypeTypeMapAttribute = GetNpgsqlDbTypeTypeMapAttribute(classProperty); + if (npgsqlDbTypeTypeMapAttribute != null) + { + var npgsqlDbTypeValue = GetNpgsqlDbTypeFromAttribute(npgsqlDbTypeTypeMapAttribute); + var npgsqlParameterType = GetNpgsqlParameterTypeFromAttribute(npgsqlDbTypeTypeMapAttribute); + var dbParameterNpgsqlDbTypeSetMethod = GetNpgsqlDbTypeFromAttributeSetMethod(npgsqlDbTypeTypeMapAttribute); + var npgsqlDbTypeAssignment = Expression.Call( + Expression.Convert(parameterVariable, npgsqlParameterType), + dbParameterNpgsqlDbTypeSetMethod, + Expression.Constant(npgsqlDbTypeValue)); + parameterAssignments.Add(npgsqlDbTypeAssignment); + } + + #endregion + + #endregion + + #region Direction + + if (dbSetting.IsDirectionSupported) + { + // Set the Parameter Direction + var directionAssignment = Expression.Call(parameterVariable, Types.DbParameterDirectionSetMethod, Expression.Constant(direction)); + parameterAssignments.Add(directionAssignment); + } + + #endregion + + #region Size + + // Set only for non-image + // By default, SQL Server only put (16 size), and that would fail if the user + // used this type for their binary columns and assign a much longer values + //if (!string.Equals(field.DatabaseType, "image", StringComparison.OrdinalIgnoreCase)) + //{ + // Set the Size + if (dbField.Size != null) + { + var sizeAssignment = Expression.Call(parameterVariable, Types.DbParameterSizeSetMethod, Expression.Constant(dbField.Size.Value)); + parameterAssignments.Add(sizeAssignment); + } + //} + + #endregion + + #region Precision + + // Set the Precision + if (dbField.Precision != null) + { + var precisionAssignment = Expression.Call(parameterVariable, Types.DbParameterPrecisionSetMethod, Expression.Constant(dbField.Precision.Value)); + parameterAssignments.Add(precisionAssignment); + } + + #endregion + + #region Scale + + // Set the Scale + if (dbField.Scale != null) + { + var scaleAssignment = Expression.Call(parameterVariable, Types.DbParameterScaleSetMethod, Expression.Constant(dbField.Scale.Value)); + parameterAssignments.Add(scaleAssignment); + } + + #endregion + + // Add the actual addition + parameterAssignments.Add(Expression.Call(dbParameterCollection, Types.DbParameterCollectionAddMethod, parameterVariable)); + + // Return the value + return Expression.Block(new[] { parameterVariable }, parameterAssignments); + } + } } diff --git a/RepoDb.Core/RepoDb/RepoDb.csproj b/RepoDb.Core/RepoDb/RepoDb.csproj index eb90e945f..e951a603f 100644 --- a/RepoDb.Core/RepoDb/RepoDb.csproj +++ b/RepoDb.Core/RepoDb/RepoDb.csproj @@ -65,7 +65,6 @@ - From 35d3744aa8fb5ff04bb88fa65efe3c6f9d286736 Mon Sep 17 00:00:00 2001 From: chrissolutions Date: Mon, 20 Apr 2020 11:37:18 +1200 Subject: [PATCH 5/6] Fixed conversion check to also check for converterMethod. --- RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs index 6d914aae8..bce50d2c1 100644 --- a/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs +++ b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs @@ -649,8 +649,8 @@ private static IEnumerable GetMemberAssignmentsForDataEntity2< // False expression var falseExpression = (Expression)Expression.Call(readerParameterExpression, readerGetValueMethod, ordinalExpression); - // Only if there are conversions, execute the logics inside - if (isConversionNeeded) + // Only if there are conversions, execute the logic inside + if (isConversionNeeded && converterMethod == null) { if (targetType.IsEnum) { @@ -752,7 +752,7 @@ private static IEnumerable GetMemberAssignmentsForDataEntity2< ordinalExpression); // Convert to correct type if necessary - if (isConversionNeeded) + if (isConversionNeeded && converterMethod == null) { valueExpression = ConvertValueExpressionForDataEntity(valueExpression, readerField, From 337aa7e361c53e5e4e13312ca4f670326f723365 Mon Sep 17 00:00:00 2001 From: chrissolutions Date: Tue, 21 Apr 2020 08:36:11 +1200 Subject: [PATCH 6/6] Added property to object handler to StringToDateClass --- .../RepoDb.IntegrationTests/TypeConversionsTest.cs | 4 ++++ RepoDb.SqLite/RepoDb.SqLite/RepoDb.SqLite.csproj | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs index b9079435a..ceaf8c44f 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs @@ -3,6 +3,7 @@ using RepoDb.Enumerations; using RepoDb.IntegrationTests.Setup; using System; +using System.Globalization; using Microsoft.Data.SqlClient; using System.Linq; using RepoDb.Entity; @@ -401,6 +402,7 @@ private class StringToDateClass : BaseEntity public StringToDateClass() { Map(p => p.ColumnDate).Convert(o => o.ToString("M'/'d'/'yyyy h:mm:ss tt")); + Map(p => p.ColumnDate).Convert(o => DateTime.Parse(o, CultureInfo.InvariantCulture)); } } @@ -441,6 +443,7 @@ private class StringToDateTimeClass : BaseEntity public StringToDateTimeClass() { Map(p => p.ColumnDateTime).Convert(o => o.ToString("M'/'d'/'yyyy h:mm:ss tt")); + Map(p => p.ColumnDateTime).Convert(o => DateTime.Parse(o, CultureInfo.InvariantCulture)); } } @@ -481,6 +484,7 @@ private class StringToDateTime2Class : BaseEntity public StringToDateTime2Class() { Map(p => p.ColumnDateTime2).Convert(o => o.ToString("M'/'d'/'yyyy h:mm:ss tt")); + Map(p => p.ColumnDateTime2).Convert(o => DateTime.Parse(o, CultureInfo.InvariantCulture)); } } diff --git a/RepoDb.SqLite/RepoDb.SqLite/RepoDb.SqLite.csproj b/RepoDb.SqLite/RepoDb.SqLite/RepoDb.SqLite.csproj index 21cb9e915..60a3528a9 100644 --- a/RepoDb.SqLite/RepoDb.SqLite/RepoDb.SqLite.csproj +++ b/RepoDb.SqLite/RepoDb.SqLite/RepoDb.SqLite.csproj @@ -35,7 +35,6 @@ -