/// <summary> /// Gets a delegate that is used to convert the <see cref="DbDataReader"/> object into data entity object. /// </summary> /// <typeparam name="TEntity">The data entity object to convert to.</typeparam> /// <param name="reader">The <see cref="DbDataReader"/> to be converted.</param> /// <returns>An instance of data entity object.</returns> public static DataReaderToDataEntityDelegate <TEntity> GetDataReaderToDataEntityDelegate <TEntity>(DbDataReader reader) where TEntity : class { var entityType = typeof(TEntity); var dynamicMethod = new DynamicMethod(StringConstant.DynamicMethod, entityType, new[] { typeof(DbDataReader) }, typeof(Assembly).GetTypeInfo().Module, true); var ilGenerator = dynamicMethod.GetILGenerator(); var fieldDefinitions = FieldDefinitionCache.Get <TEntity>(Command.Query); // Declare IL Variables ilGenerator.DeclareLocal(entityType); ilGenerator.DeclareLocal(typeof(object)); // Create instance of the object type ilGenerator.Emit(OpCodes.Newobj, entityType.GetTypeInfo().GetConstructor(Type.EmptyTypes)); ilGenerator.Emit(OpCodes.Stloc, 0); // Matching the fields var fields = Enumerable.Range(0, reader.FieldCount) .Select(reader.GetName) .Select(n => n.ToLower()) .ToList(); var matchedCount = 0; // Iterate the properties PropertyCache.Get <TEntity>(Command.Query) .Where(property => property.PropertyInfo.CanWrite) .ToList() .ForEach(property => { var mappedName = property.GetMappedName().ToLower(); var fieldDefinition = fieldDefinitions?.FirstOrDefault(fd => fd.Name.ToLower() == mappedName); var ordinal = fields.IndexOf(mappedName); if (ordinal >= 0) { EmitDataReaderToDataEntityMapping <TEntity>(ilGenerator, ordinal, property, fieldDefinition); matchedCount++; } }); // Throw an error if there are no matching atleast one if (matchedCount == 0) { throw new NoMatchedFieldsException($"There is no matching fields between the result set of the data reader and the type '{typeof(TEntity).FullName}'."); } // Return the TEntity instance value ilGenerator.Emit(OpCodes.Ldloc, 0); ilGenerator.Emit(OpCodes.Ret); // Create a delegate return((DataReaderToDataEntityDelegate <TEntity>)dynamicMethod.CreateDelegate(typeof(DataReaderToDataEntityDelegate <TEntity>))); }
/// <summary> /// Returns the list of the bindings for the entity. /// </summary> /// <typeparam name="TEntity">The target entity type.</typeparam> /// <param name="newEntityExpression">The new entity expression.</param> /// <param name="readerParameterExpression">The data reader parameter.</param> /// <param name="readerFields">The list of fields to be bound from the data reader.</param> /// <returns>The enumerable list of member assignment and bindings.</returns> private static IEnumerable <MemberAssignment> GetMemberAssignments <TEntity>(Expression newEntityExpression, ParameterExpression readerParameterExpression, IEnumerable <DataReaderFieldDefinition> readerFields) where TEntity : class { // Initialize variables var memberAssignments = new List <MemberAssignment>(); var dataReaderType = typeof(DbDataReader); var tableFields = FieldDefinitionCache.Get <TEntity>(); var isStrict = TypeMapper.ConversionType == ConversionType.Default; // Iterate each properties foreach (var property in PropertyCache.Get <TEntity>().Where(property => property.PropertyInfo.CanWrite)) { // Gets the mapped name and the ordinal var mappedName = property.GetMappedName().ToLower(); var ordinal = readerFields?.Select(f => f.Name).ToList().IndexOf(mappedName); // Process only if there is a correct ordinal if (ordinal >= 0) { // Variables needed for the iteration var tableField = tableFields?.FirstOrDefault(f => f.Name.ToLower() == mappedName); var readerField = readerFields.First(f => f.Name.ToLower() == mappedName); var isTableFieldNullable = tableField == null || tableField?.IsNullable == true; var underlyingType = Nullable.GetUnderlyingType(property.PropertyInfo.PropertyType); var propertyType = underlyingType ?? property.PropertyInfo.PropertyType; var convertType = readerField.Type; var isConversionNeeded = readerField.Type != propertyType; // Get the correct method info, if the reader.Get<Type> is not found, then use the default GetValue() method var readerGetValueMethod = dataReaderType.GetTypeInfo().GetMethod(string.Concat("Get", readerField.Type.Name)); if (readerGetValueMethod == null) { // Single value is throwing an exception in GetString(), skip it and use the GetValue() instead if (isStrict == false && readerField.Type != typeof(Single)) { readerGetValueMethod = dataReaderType.GetTypeInfo().GetMethod(string.Concat("Get", propertyType.Name)); } // If present, then use the property type, otherwise, use the object if (readerGetValueMethod != null) { convertType = propertyType; } else { readerGetValueMethod = dataReaderType.GetTypeInfo().GetMethod("GetValue"); convertType = typeof(object); } // Force the conversion flag isConversionNeeded = true; } // Expressions var ordinalExpression = Expression.Constant(ordinal); var valueExpression = (Expression)null; // Check for nullables if (isTableFieldNullable == true) { var isDbNullExpression = Expression.Call(readerParameterExpression, dataReaderType.GetTypeInfo().GetMethod("IsDBNull"), ordinalExpression); // True expression var trueExpression = (Expression)null; if (underlyingType != null && underlyingType.GetTypeInfo().IsValueType == true) { trueExpression = Expression.New(typeof(Nullable <>).MakeGenericType(propertyType)); } else { trueExpression = Expression.Default(propertyType); } // False expression var falseExpression = (Expression)Expression.Call(readerParameterExpression, readerGetValueMethod, ordinalExpression); // Only if there are conversions, execute the logics inside if (isConversionNeeded == true) { if (isStrict == true) { falseExpression = Expression.Convert(falseExpression, propertyType); } else { // Variables needed var targetInstance = (Expression)null; var targetMethod = (MethodInfo)null; var targetParameter = (Expression)null; // Identify if the target type is 'Guid' if (propertyType == typeof(Guid) && readerField.Type == typeof(string)) { // This is Guid.Parse() targetMethod = typeof(Guid).GetTypeInfo().GetMethod("Parse", new[] { typeof(string) }); targetInstance = null; targetParameter = falseExpression; } else if (propertyType == typeof(string) && readerField.Type == typeof(Guid)) { // This is Guid.ToString() targetMethod = typeof(Guid).GetTypeInfo().GetMethod("ToString", new Type[0]); targetInstance = falseExpression; targetParameter = null; } else { // This System.Convert.To<Type>() targetMethod = typeof(Convert).GetTypeInfo().GetMethod(string.Concat("To", propertyType.Name), new[] { convertType }); targetInstance = null; targetParameter = falseExpression; } // If there are methods found from System.Convert(), then use it, otherwise use the normal if (targetMethod != null) { if (targetParameter == null) { falseExpression = Expression.Call(targetInstance, targetMethod); } else { falseExpression = Expression.Call(targetInstance, targetMethod, targetParameter); } } else { // There are coersion problem on certain types (i.e: Guid-to-String (vice versa)) falseExpression = Expression.Convert(falseExpression, propertyType); } } } if (underlyingType != null && underlyingType.GetTypeInfo().IsValueType == true) { var nullableConstructorExpression = typeof(Nullable <>).MakeGenericType(propertyType).GetTypeInfo().GetConstructor(new[] { propertyType }); falseExpression = Expression.New(nullableConstructorExpression, falseExpression); } // Set the value valueExpression = Expression.Condition(isDbNullExpression, trueExpression, falseExpression); } else { // Call the actual Get<Type>/GetValue method by ordinal valueExpression = Expression.Call(readerParameterExpression, readerGetValueMethod, ordinalExpression); // Convert to correct type if necessary if (isConversionNeeded == true) { valueExpression = Expression.Convert(valueExpression, propertyType); } // Set for the 'Nullable' property if (underlyingType != null && underlyingType.GetTypeInfo().IsValueType == true) { var nullableConstructorExpression = typeof(Nullable <>).MakeGenericType(propertyType).GetTypeInfo().GetConstructor(new[] { propertyType }); valueExpression = Expression.New(nullableConstructorExpression, valueExpression); } } // Set the actual property value memberAssignments.Add(Expression.Bind(property.PropertyInfo, valueExpression)); } } // Return the result return(memberAssignments); }
/// <summary> /// Returns the list of the bindings for the entity. /// </summary> /// <typeparam name="TEntity">The target entity type.</typeparam> /// <param name="newEntityExpression">The new entity expression.</param> /// <param name="readerParameterExpression">The data reader parameter.</param> /// <param name="readerFields">The list of fields to be bound from the data reader.</param> /// <returns>The enumerable list of member assignment and bindings.</returns> private static IEnumerable <MemberAssignment> GetMemberAssignments <TEntity>(Expression newEntityExpression, ParameterExpression readerParameterExpression, IEnumerable <DataReaderFieldDefinition> readerFields) where TEntity : class { // Initialize variables var memberAssignments = new List <MemberAssignment>(); var dataReaderType = typeof(DbDataReader); var tableFields = FieldDefinitionCache.Get <TEntity>(); var properties = PropertyCache.Get <TEntity>(Command.Query) .Where(property => property.PropertyInfo.CanWrite) .ToList(); // Iterate each properties properties?.ForEach(property => { // Gets the mapped name and the ordinal var mappedName = property.GetMappedName().ToLower(); var ordinal = readerFields?.Select(f => f.Name).ToList().IndexOf(mappedName); // Process only if there is a correct ordinal if (ordinal >= 0) { // Variables needed for the iteration var tableField = tableFields?.FirstOrDefault(f => f.Name.ToLower() == mappedName); var readerField = readerFields.First(f => f.Name.ToLower() == mappedName); var isTableFieldNullable = tableField == null || tableField?.IsNullable == true; var underlyingType = Nullable.GetUnderlyingType(property.PropertyInfo.PropertyType); var propertyType = underlyingType ?? property.PropertyInfo.PropertyType; var isConversionNeeded = readerField?.Type != propertyType; // Get the correct method info, if the reader.Get<Type> is not found, then use the default GetValue var readerGetValueMethod = dataReaderType.GetMethod(string.Concat("Get", readerField?.Type.Name)); if (readerGetValueMethod == null) { readerGetValueMethod = dataReaderType.GetMethod(string.Concat("Get", propertyType.Name)) ?? dataReaderType.GetMethod("GetValue"); isConversionNeeded = true; // Force } // Expressions var ordinalExpression = Expression.Constant(ordinal); var valueExpression = (Expression)null; // Check for nullables if (isTableFieldNullable == true) { var isDbNullExpression = Expression.Call(readerParameterExpression, dataReaderType.GetMethod("IsDBNull"), ordinalExpression); // True expression var trueExpression = (Expression)null; if (underlyingType != null && underlyingType.IsValueType == true) { trueExpression = Expression.New(typeof(Nullable <>).MakeGenericType(propertyType)); } else { trueExpression = Expression.Default(propertyType); } // False expression var falseExpression = (Expression)Expression.Call(readerParameterExpression, readerGetValueMethod, ordinalExpression); if (isConversionNeeded == true) { falseExpression = Expression.Convert(falseExpression, propertyType); } if (underlyingType != null && underlyingType.IsValueType == true) { var nullableConstructorExpression = typeof(Nullable <>).MakeGenericType(propertyType).GetConstructor(new[] { propertyType }); falseExpression = Expression.New(nullableConstructorExpression, falseExpression); } // Set the value valueExpression = Expression.Condition(isDbNullExpression, trueExpression, falseExpression); } else { // Call the actual Get<Type>/GetValue method by ordinal valueExpression = Expression.Call(readerParameterExpression, readerGetValueMethod, ordinalExpression); // Convert to correct type if necessary if (isConversionNeeded == true) { valueExpression = Expression.Convert(valueExpression, propertyType); } // Set for the 'Nullable' property if (underlyingType != null && underlyingType.IsValueType == true) { var nullableConstructorExpression = typeof(Nullable <>).MakeGenericType(propertyType).GetConstructor(new[] { propertyType }); valueExpression = Expression.New(nullableConstructorExpression, valueExpression); } } // Set the actual property value memberAssignments.Add(Expression.Bind(property.PropertyInfo, valueExpression)); } }); // Return the result return(memberAssignments); }