AutoMapper:IDataReader和DTO对象之间的映射

时间:2009-12-29 07:19:18

标签: c# automapper

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

我已经能够在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; }
}

2 个答案:

答案 0 :(得分:2)

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

应用于coloumn-mapping的Business对象的属性。

/// <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 to BusinessObject转换方法,它围绕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);
                }
            }
        }
    }

答案 1 :(得分: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;
    }