2009-12-29 6 views
4

저장 프로 시저의 결과가 포함 된 DataReader가 있습니다. 열에 대한 이름 지정 규칙은 공백에 밑줄을 사용합니다.AutoMapper : IDataReader 개체와 DTO 개체 간의 매핑

필드가 정확히 일치하는 경우에만 IDataReader와 IEnumerable간에 성공적으로 매핑 할 수있었습니다. 내 객체의 필드 이름을 지정하는 저장 프로 시저에 사용되는 명명 규칙이 필요하지 않습니다. 그리고 데이터베이스 측면에서도 마찬가지입니다. 나는 DBA에게 파스칼 사건을 성공적으로 집행 할 것이라고 생각하지 않는다.

저는 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

이 작업을 위해 사용자 지정 특성을 작성했습니다. 리플렉션을 통해 매핑 할당을 수행하고 여기에 몇 가지 샘플 코드가 있습니다.

coloumn-mapping에 대한 Business Object 등록 정보에 적용되는 속성.

/// <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) 
    { 
    } 
} 

여기서 확장 메서드를 호출 감긴 BusinessObject transacformation의 방법에의 DataRow이다.

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