[英]AutoMapper: Mapping between a IDataReader and DTO object
我有一個DataReader,其中包含存儲過程的結果。 列的命名約定使用下划線表示空格。
我已經能夠在IDataReader和IEnumerable之間成功映射,但前提是字段完全匹配。 我不想使用存儲過程中的命名約定來規定我在對象中命名字段的方式。 在數據庫方面也是如此。 我認為我不會在DBA上成功執行Pascal Case。
我想避免必須對需要映射的每個字段使用ForMember()。 那會破壞使用AutoMapper的目的。
我找到了關於該主題的先前文章 ,在測試中已將其用作參考。 我無法獲得正確的配置/映射才能成功通過測試。 我希望有人可以提供幫助。
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; }
}
我們已經編寫了自定義屬性來實現此目的。 我們借助反射進行映射分配,這是一些示例代碼。
應用於業務對象屬性以進行對話映射的屬性。
/// <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的轉換方法,該方法包裝在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);
}
}
}
}
我下載了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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.