2009-12-29 7 views
4

J'ai un DataReader qui contient le résultat d'une procédure stockée. La convention de dénomination des colonnes utilise des traits de soulignement pour les espaces.AutoMapper: Mappage entre un IDataReader et un objet DTO

J'ai réussi à mapper entre IDataReader et IEnumerable, mais seulement si les champs correspondent exactement. Je ne veux pas que la convention de nommage utilisée dans les procédures stockées dicte la façon dont je nomme les champs dans mes objets. Et la même chose est vraie du côté de la base de données. Je ne pense pas que je réussirais à appliquer Pascal Case sur les DBA. Je voudrais éviter d'avoir à utiliser ForMember() pour chaque champ que j'ai besoin de mapper. Cela irait à l'encontre de l'objectif d'utiliser AutoMapper.

J'ai trouvé un previous post sur le sujet que j'ai utilisé comme référence dans mes tests. Je n'ai pas réussi à obtenir la configuration/mappage correct pour que le test réussisse. J'espère que quelqu'un peut aider.

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

Répondre

3

Nous avons écrit des attributs personnalisés pour y parvenir. Nous effectuons la cartographie-affectation avec l'aide de la réflexion et voici quelques exemples de code pour vous.

L'attribut qui est appliqué aux propriétés d'objet métier pour le mappage de coloumn.

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

Un exemple d'objet métier ressemblerait à ceci dans mon application.

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

Voici le DataRow à BusinessObject méthode transacformation qui est enroulé autour de l'appel d'une méthode d'extension.

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

J'ai téléchargé la source AutoMapper et j'ai pu effectuer un débogage. J'ai dû modifier la méthode CreateBuilder dans DataReaderMapper.cs pour que le test réussisse.

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; 
    } 
Questions connexes