2009-12-29 44 views
4

我有一个包含存储过程结果的DataReader。列的命名约定对空格使用下划线。AutoMapper:IDataReader和DTO对象之间的映射

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

我想避免使用需要映射的ForMember()foreach字段。这将破坏使用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; } 
} 

回答

3

我们编写了自定义属性来实现这一点。我们在反射的帮助下进行映射分配,下面给出一些示例代码。

应用于业务对象验证的颜色映射属性。

/// <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的transacformation方法,它是一个扩展方法的调用缠。

/// <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); 
       } 
      } 
     } 
    } 
0

我下载了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; 
    }