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/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..ceaf8c44f 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/TypeConversionsTest.cs @@ -3,8 +3,10 @@ using RepoDb.Enumerations; using RepoDb.IntegrationTests.Setup; using System; +using System.Globalization; using Microsoft.Data.SqlClient; using System.Linq; +using RepoDb.Entity; namespace RepoDb.IntegrationTests { @@ -391,15 +393,21 @@ public void TestSqlConnectionCrudConvertionFromStringToSmallMoney() #region StringToDateClass [Map("CompleteTable")] - private class StringToDateClass + private class StringToDateClass : BaseEntity { [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")); + Map(p => p.ColumnDate).Convert(o => DateTime.Parse(o, CultureInfo.InvariantCulture)); + } } [TestMethod] - public void TestSqlConnectionCrudConvertionFromStringToDate() + public void TestSqlConnectionCrudConversionFromStringToDate() { // Setup var entity = new StringToDateClass @@ -426,15 +434,21 @@ public void TestSqlConnectionCrudConvertionFromStringToDate() #region StringToDateTimeClass [Map("CompleteTable")] - private class StringToDateTimeClass + private class StringToDateTimeClass : BaseEntity { [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")); + Map(p => p.ColumnDateTime).Convert(o => DateTime.Parse(o, CultureInfo.InvariantCulture)); + } } [TestMethod] - public void TestSqlConnectionCrudConvertionFromStringToDateTime() + public void TestSqlConnectionCrudConversionFromStringToDateTime() { // Setup var entity = new StringToDateTimeClass @@ -461,15 +475,21 @@ public void TestSqlConnectionCrudConvertionFromStringToDateTime() #region StringToDateTime2Class [Map("CompleteTable")] - private class StringToDateTime2Class + private class StringToDateTime2Class : BaseEntity { [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")); + Map(p => p.ColumnDateTime2).Convert(o => DateTime.Parse(o, CultureInfo.InvariantCulture)); + } } [TestMethod] - public void TestSqlConnectionCrudConvertionFromStringToDateTime2() + public void TestSqlConnectionCrudConversionFromStringToDateTime2() { // Setup var entity = new StringToDateTime2Class diff --git a/RepoDb.Core/RepoDb/Entity/BaseEntity.cs b/RepoDb.Core/RepoDb/Entity/BaseEntity.cs new file mode 100644 index 000000000..1058ec112 --- /dev/null +++ b/RepoDb.Core/RepoDb/Entity/BaseEntity.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq.Expressions; +using RepoDb.Extensions; +using RepoDb.Interfaces; + +namespace RepoDb.Entity +{ + /// + /// + /// + /// + public abstract class BaseEntity where TEntity : class + { + /// + /// + /// + public static readonly IPropertyMap PropertyMap = new EntityPropertyMap(); + + static BaseEntity() + { + 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 4ee8217f3..79a475797 100644 --- a/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs +++ b/RepoDb.Core/RepoDb/Extensions/EnumerableExtension.cs @@ -83,5 +83,47 @@ 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. + /// + /// 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/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/FunctionFactory.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs index fb174a634..bce50d2c1 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(BaseEntity)) + ? 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)BaseEntity.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 logic inside + if (isConversionNeeded && converterMethod == null) + { + 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 && converterMethod == null) + { + 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/Reflection/FunctionFactory2.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs new file mode 100644 index 000000000..f589381ee --- /dev/null +++ b/RepoDb.Core/RepoDb/Reflection/FunctionFactory2.cs @@ -0,0 +1,2824 @@ +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 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 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); + + 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 + + #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 + { + // Variables for arguments + 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 DbParameterCollection + var dbParameterCollection = Expression.Property(commandParameterExpression, Types.DbCommandParametersProperty); + + // 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(Types.TypeOfType, "instanceType"); + + // Input fields properties + inputFields?.ForEach((x, i) => + { + propertyVariableList.Add(new + { + Index = i, + Field = x, + Direction = ParameterDirection.Input + }); + }); + + // Output fields properties + outputFields?.ForEach(x => + { + propertyVariableList.Add(new + { + Index = propertyVariableList.Count, + Field = x, + Direction = ParameterDirection.Output + }); + }); + + // Variables for expression body + var bodyExpressions = new List + { + Expression.Call(dbParameterCollection, Types.DbParameterCollectionClearMethod) + }; + + // Clear the parameter collection first + + // 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 == Types.TypeOfObject) + { + 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) + }); + } + 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 = 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) + { + 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 typeOfListEntity = typeof(IList); + var typeOfEntity = typeof(TEntity); + + // Variables for arguments + var commandParameterExpression = Expression.Parameter(Types.TypeOfDbCommand, "command"); + var entitiesParameterExpression = Expression.Parameter(typeOfListEntity, "entities"); + + // Variables for types + var entityProperties = PropertyCache.Get(); + + // Variables for DbParameterCollection + var dbParameterCollection = Expression.Property(commandParameterExpression, Types.DbCommandParametersProperty); + + // Variables for List + var listIndexerMethod = typeOfListEntity.GetMethod("get_Item", new[] { Types.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(Types.TypeOfType, "instanceType"); + + // Input fields properties + inputFields?.ForEach((x, i) => + { + propertyVariableList.Add(new + { + Index = i, + Field = x, + Direction = ParameterDirection.Input + }); + }); + + // Output fields properties + outputFields?.ForEach(x => + { + propertyVariableList.Add(new + { + Index = propertyVariableList.Count, + Field = x, + Direction = ParameterDirection.Output + }); + }); + + // Variables for expression body + var bodyExpressions = new List + { + Expression.Call(dbParameterCollection, Types.DbParameterCollectionClearMethod) + }; + + // Clear the parameter collection first + + // 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 {instanceVariable}; + + // Entity instance + 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 == Types.TypeOfObject) + { + 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) + }); + } + 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 = 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) + { + 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 + ) where TEntity : class + { + // Parameters for the block + var typeOfEntity = typeof(TEntity); + 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, Types.DbCommandCreateParameterMethod); + parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); + + // Set the name + var nameAssignment = Expression.Call(parameterVariable, Types.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) + { + // Set the value + + // 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 }), 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 + var 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 + ( + 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 typeOfEntity = typeof(TEntity); + 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, Types.DbCommandCreateParameterMethod); + parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); + + // Set the name + var nameAssignment = Expression.Call(parameterVariable, Types.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 propertyMap = (EntityPropertyMap)BaseEntity.PropertyMap; + var converterMethod = (Delegate)((EntityPropertyConverter)propertyMap?.Find(classProperty.PropertyInfo))?.ToObject; + + #region Value + + // Set the value + if (skipValueAssignment == false) + { + // Set the value + + // 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 + Expression value; + + // 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 }), 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 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); + 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 + 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) + { + // Identification of the DBNull + var valueVariable = Expression.Variable(Types.TypeOfObject, string.Concat("valueOf", parameterName)); + var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); + + // Set the property value + valueBlock = Expression.Block(new[] { valueVariable }, + Expression.Assign(valueVariable, value), + Expression.Condition(valueIsNull, dbNullValue, valueVariable)); + } + else + { + valueBlock = value; + } + + // Add to the collection + var valueAssignment = Expression.Call(parameterVariable, Types.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, 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); + } + + 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.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 @@ -