【问题标题】:AutoMapper: Mapping between a IDataReader and DTO objectAutoMapper:IDataReader 和 DTO 对象之间的映射
【发布时间】:2017-01-13 04:02:37
【问题描述】:

我有一个包含存储过程结果的 DataReader。列的命名约定使用下划线表示空格。

我已经能够成功地在 IDataReader 和 IEnumerable 之间进行映射,但前提是字段完全匹配。我不希望存储过程中使用的命名约定来规定我在对象中命名字段的方式。在数据库端也是如此。我认为我不会成功地对 DBA 执行 Pascal Case。

我想避免对我需要映射的每个字段使用 ForMember()。这将破坏使用 AutoMapper 的目的。

我在该主题上找到了previous post,我在测试中将其用作参考。我无法获得正确的配置/映射以使测试成功通过。我希望有人可以提供帮助。

public class DataReaderTests
{
    private DTOObject _result;
    private IDataReader _dataReader;

    protected override void Establish_context()
    {
        Mapper.Initialize(cfg =>
        {
            cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
            cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
            cfg.CreateMap<IDataReader, IEnumerable<DTOObject>>();
        });

        _dataReader = new DataBuilder().BuildDataReader();
        _result = Mapper.Map<IDataReader, IEnumerable<DTOObject>>(_dataReader).FirstOrDefault();
    }

    [Test]
    public void Then_a_column_containing_phone_number_should_be_read()
    {
        Assert.That(_result.PhoneNumber, Is.EqualTo(_dataReader[FieldName.PhoneNumber]));
    }
}

public class DataBuilder
{
    public IDataReader BuildDataReader()
    {
        var resultData = new DataTable();    
        resultData.Columns.Add(FieldName.PhoneNumber, typeof(string));

        var resultDataRow = resultData.NewRow();
        resultDataRow[FieldName.PhoneNumber] = "111-222-3333";

        resultData.Rows.Add(resultDataRow);

        return resultData.CreateDataReader();
    }
}

internal class FieldName
{
    public const String Id = "id";
    public const String Name = "name";
    public const String PhoneNumber = "phone_number";
    public const String CreateDate = "create_date";   
}

public class DTOObject
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string PhoneNumber { get; set; }
    public DateTime CreatedDate { get; set; }
}

【问题讨论】:

    标签: c# automapper


    【解决方案1】:

    我们已经编写了自定义属性来实现这一点。我们在反射的帮助下进行映射分配,这里有一些示例代码供您参考。

    应用于列映射的业务对象属性的属性。

    /// <summary>
        /// Holds mapping information between business objects properties and database table fields.
        /// </summary>
        [global::System.AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
        public sealed class DataFieldMappingAttribute : Attribute
        {
            /// <summary>
            /// Initializes a new instance of the DataFieldMappingAttribute class.
            /// </summary>
            /// <param name="fieldName">Name of the Field in Database Table that the business object properties maps to.</param>
            public DataFieldMappingAttribute(string fieldName)
            {
                this.MappedField = fieldName;
            }
    
            /// <summary>
            /// Gets or Sets the mapped Database Table Field.
            /// </summary>
            public string MappedField
            {
                get;
                private set;
            }
        }
    

    示例业务对象在我的应用程序中如下所示。

    User.cs

    [TableMapping("Users")]
    public class User : EntityBase
    {
        #region Constructor(s)
        public AppUser()
        {
            BookCollection = new BookCollection();
        }
        #endregion
    
        #region Properties
    
        #region Default Properties - Direct Field Mapping using DataFieldMappingAttribute
    
        private System.Int32 _UserId;
    
        private System.String _FirstName;
        private System.String _LastName;
        private System.String _UserName;
        private System.Boolean _IsActive;
    
        [DataFieldMapping("UserID")]
        [DataObjectFieldAttribute(true, true, false)]
        [NotNullOrEmpty(Message = "UserID From Users Table Is Required.")]
        public override int Id
        {
            get
            {
                return _UserId;
            }
            set
            {
                _UserId = value;
            }
        }
    
        [DataFieldMapping("UserName")]
        [Searchable]
        [NotNullOrEmpty(Message = "Username Is Required.")]
        public string UserName
        {
            get
            {
                return _UserName;
            }
            set
            {
                _UserName = value;
            }
        }
    
        [DataFieldMapping("FirstName")]
        [Searchable]
        public string FirstName
        {
            get
            {
                return _FirstName;
            }
            set
            {
                _FirstName = value;
            }
        }
    
        [DataFieldMapping("LastName")]
        [Searchable]
        public string LastName
        {
            get
            {
                return _LastName;
            }
            set
            {
                _LastName = value;
            }
        }
    
        [DataFieldMapping("IsActive")]
        public bool IsActive
        {
            get
            {
                return _IsActive;
            }
            set
            {
                _IsActive = value;
            }
        }
    
        #region One-To-Many Mappings
        public BookCollection Books { get; set; }
    
        #endregion
    
        #region Derived Properties
        public string FullName { get { return this.FirstName + " " + this.LastName; } }
    
        #endregion
    
        #endregion
    
        public override bool Validate()
        {
            bool baseValid = base.Validate();
            bool localValid = Books.Validate();
            return baseValid && localValid;
        }
    }
    

    BookCollection.cs

    /// <summary>
    /// The BookCollection class is designed to work with lists of instances of Book.
    /// </summary>
    public class BookCollection : EntityCollectionBase<Book>
    {
        /// <summary>
        /// Initializes a new instance of the BookCollection class.
        /// </summary>
        public BookCollection()
        {
        }
    
        /// <summary>
        /// Initializes a new instance of the BookCollection class.
        /// </summary>
        public BookCollection (IList<Book> initialList)
            : base(initialList)
        {
        }
    }
    

    这是 DataRow 到 BusinessObject 的转换方法,它围绕着扩展方法的调用。

        /// <summary>
        /// Transforms DataRow into business object.
        /// </summary>
        /// <typeparam name="TEntity">A type that inherits EntityBase.</typeparam>
        /// <typeparam name="TDataRow">A type that inherits DataRow.</typeparam>
        /// <param name="dataRow">DataRow object which is transformed from business object.</param>
        /// <param name="entity">business object which is transformed into DataRow object.</param>
        public static void TransformDataRowToEntity<TEntity, TDataRow>(ref TDataRow dataRow, ref TEntity entity)
            where TDataRow : DataRow
            where TEntity : EntityBase
        {
            IQueryable<DataField> entityFields = entity.GetDataFields();
    
            foreach (var entityField in entityFields)
            {
                if (dataRow[entityField.DataFieldMapping.MappedField] is System.DBNull)
                {
                    entityField.Property.SetValue(entity, null, null);
                }
                else
                {
                    if (entityField.Property.GetType().IsEnum)
                    {
                        Type enumType = entityField.Property.GetType();
                        EnumConverter enumConverter = new EnumConverter(enumType);
                        object enumValue = enumConverter.ConvertFrom(dataRow[entityField.DataFieldMapping.MappedField]);
                        entityField.Property.SetValue(entity, enumValue, null);
                    }
                    else
                    {
                        entityField.Property.SetValue(entity, dataRow[entityField.DataFieldMapping.MappedField], null);
                    }
                }
            }
        }
    

    【讨论】:

      【解决方案2】:

      我下载了 AutoMapper 源并能够进行一些调试。我必须更改 DataReaderMapper.cs 中的 CreateBuilder 方法才能通过测试。

          private static Build CreateBuilder(Type destinationType, IDataRecord dataRecord)
          {
              var method = new DynamicMethod("DynamicCreate", destinationType, new[] { typeof(IDataRecord) }, destinationType, true);
              var generator = method.GetILGenerator();
      
              var result = generator.DeclareLocal(destinationType);
              generator.Emit(OpCodes.Newobj, destinationType.GetConstructor(Type.EmptyTypes));
              generator.Emit(OpCodes.Stloc, result);
      
              for (var i = 0; i < dataRecord.FieldCount; i++)
              {
                  var propertyInfo = destinationType.GetProperty(ConvertLowerUnderscoreNamingToPascalNaming(dataRecord.GetName(i)));
                  var endIfLabel = generator.DefineLabel();
      
                  if (propertyInfo != null && propertyInfo.GetSetMethod(true) != null)
                  {
                      generator.Emit(OpCodes.Ldarg_0);
                      generator.Emit(OpCodes.Ldc_I4, i);
                      generator.Emit(OpCodes.Callvirt, isDBNullMethod);
                      generator.Emit(OpCodes.Brtrue, endIfLabel);
      
                      generator.Emit(OpCodes.Ldloc, result);
                      generator.Emit(OpCodes.Ldarg_0);
                      generator.Emit(OpCodes.Ldc_I4, i);
                      generator.Emit(OpCodes.Callvirt, getValueMethod);
                      generator.Emit(OpCodes.Unbox_Any, dataRecord.GetFieldType(i));
                      generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod(true));
      
                      generator.MarkLabel(endIfLabel);
                  }
              }
      
              generator.Emit(OpCodes.Ldloc, result);
              generator.Emit(OpCodes.Ret);
      
              return (Build)method.CreateDelegate(typeof(Build));
          }
      
          //TODO: refactor to use INamingConvetion and resolve with RegEx pattern
          private static string ConvertLowerUnderscoreNamingToPascalNaming(string original)
          {
              var LowerOriginal = original.ToLower();
              string[] tokens = LowerOriginal.Split('_');
      
              string converted = "";
      
              foreach (var token in tokens)
                  converted += token.Substring(0, 1).ToUpper() + token.Substring(1);
      
              return converted;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-02-19
        • 2019-05-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多