我已经弯曲了一段时间,但我想我错过了什么,所以可能有人会帮忙。
我们说我有以下映射器类:
public class Mapping<TSource, TResult>
{
private readonly Action<TSource, TResult> setter;
public Mapping(Expression<Func<TSource, TResult>> expression)
{
var newValue = Expression.Parameter(expression.Body.Type);
var body = Expression.Assign(expression.Body, newValue);
var assign = Expression.Lambda<Action<TSource, TResult>>(body, expression.Parameters[0], newValue);
setter = assign.Compile();
}
public void Assign(TSource instance, TResult value)
{
setter(instance, value);
}
}
它工作正常:
[Test]
public void ShouldMapProperty()
{
var testClass = new TestClass();
var nameMapping = new Mapping<TestClass, string>(x => x.Name);
var ageMapping = new Mapping<TestClass, int>(x => x.Age);
nameMapping.Assign(testClass, "name");
ageMapping.Assign(testClass, 10);
Assert.AreEqual("name", testClass.Name);
Assert.AreEqual(10, testClass.Age);
}
事情是,我想将单个对象类型的映射保留到某个集合中,并且只要不同的属性具有不同的类型,TResult就会受到影响。 如何很好地摆脱TResult?
更新 看起来我还不够清楚,所以我将如何使用它:
public class Mapping<TSource, TResult>
{
private readonly Action<TSource, TResult> setter;
private readonly string columnName;
public Mapping(Expression<Func<TSource, TResult>> expression, string columnName)
{
this.columnName = columnName;
var newValue = Expression.Parameter(expression.Body.Type);
var body = Expression.Assign(expression.Body, newValue);
var assign = Expression.Lambda<Action<TSource, TResult>>(body, expression.Parameters[0], newValue);
setter = assign.Compile();
}
public void Assign(TSource instance, DataRow row)
{
setter(instance, row[columnName]);
}
}
然后我会有一些MappingConfiguration类,让我这样做:
MappingConfiguration.For<TestClass>()
.Map(x => x.Name, "FirstName")
.Map(x => x.Age, "Age");
最后一些MappingEngine类,它将DataTable和MappingConfiguration作为输入并生成IEnumerable<TestClass>
作为输出。
更新2: 我已经修改了初始版本:
public class Mapping2<TSource>
{
private readonly Delegate setter;
public Mapping2(Expression<Func<TSource, object>> expression)
{
var newValue = Expression.Parameter(expression.Body.Type);
var body = Expression.Assign(expression.Body, newValue);
var assign = Expression.Lambda(body, expression.Parameters[0], newValue);
setter = assign.Compile();
}
public void Assign(TSource instance, object value)
{
setter.DynamicInvoke(instance, value);
}
}
它几乎可以工作 几乎我的意思是它适用于引用类型属性,并且我得到值类型属性:
System.ArgumentException:表达式必须是可写的 参数名称:左
答案 0 :(得分:2)
我设法做到了,源代码如下。它运行速度比Automapper快一点(不确定我的Automapper配置是否是这项任务的最快速度),基准测试不是防弹的,但是在我的机器上使用我的书面映射器和使用Automapper的39.90来映射500万行需要20.16秒,尽管看起来似乎Automapper使用较少的内存来执行此任务(尚未测量,但有1000万行Automapper会产生结果,而我的映射器会因OutOfMemory而失败)。
public class MappingParameter<TSource>
{
private readonly Delegate setter;
private MappingParameter(Delegate compiledSetter)
{
setter = compiledSetter;
}
public static MappingParameter<TSource> Create<TResult>(Expression<Func<TSource, TResult>> expression)
{
var newValue = Expression.Parameter(expression.Body.Type);
var body = Expression.Assign(expression.Body, newValue);
var assign = Expression.Lambda(body, expression.Parameters[0], newValue);
var compiledSetter = assign.Compile();
return new MappingParameter<TSource>(compiledSetter);
}
public void Assign(TSource instance, object value)
{
object convertedValue;
if (!setter.Method.ReturnType.IsAssignableFrom(typeof(string)))
{
convertedValue = Convert.ChangeType(value, setter.Method.ReturnType);
}
else
{
convertedValue = value;
}
setter.DynamicInvoke(instance, convertedValue);
}
}
public class DataRowMappingConfiguration<TSource>
{
private readonly Dictionary<string, MappingParameter<TSource>> mappings =
new Dictionary<string, MappingParameter<TSource>>();
public DataRowMappingConfiguration<TSource> Add<TResult>(string columnName,
Expression<Func<TSource, TResult>> expression)
{
mappings.Add(columnName, MappingParameter<TSource>.Create(expression));
return this;
}
public Dictionary<string, MappingParameter<TSource>> Mappings
{
get
{
return mappings;
}
}
}
public class DataRowMapper<TSource>
{
private readonly DataRowMappingConfiguration<TSource> configuration;
public DataRowMapper(DataRowMappingConfiguration<TSource> configuration)
{
this.configuration = configuration;
}
public IEnumerable<TSource> Map(DataTable table)
{
var list = new List<TSource>(table.Rows.Count);
foreach (DataRow dataRow in table.Rows)
{
var obj = (TSource)Activator.CreateInstance(typeof(TSource));
foreach (var mapping in configuration.Mappings)
{
mapping.Value.Assign(obj, dataRow[mapping.Key]);
}
list.Add(obj);
}
return list;
}
}
public class TestClass
{
public string Name { get; set; }
public int Age { get; set; }
}
[TestFixture]
public class DataRowMappingTests
{
[Test]
public void ShouldMapPropertiesUsingOwnMapper()
{
var mappingConfiguration = new DataRowMappingConfiguration<TestClass>()
.Add("firstName", x => x.Name)
.Add("age", x => x.Age);
var mapper = new DataRowMapper<TestClass>(mappingConfiguration);
var dataTable = new DataTable();
dataTable.Columns.Add("firstName");
dataTable.Columns.Add("age");
for (int i = 0; i < 5000000; i++)
{
var row = dataTable.NewRow();
row["firstName"] = "John";
row["age"] = 15;
dataTable.Rows.Add(row);
}
var start = DateTime.Now;
var result = mapper.Map(dataTable).ToList();
Console.WriteLine((DateTime.Now - start).TotalSeconds);
Assert.AreEqual("John", result.First().Name);
Assert.AreEqual(15, result.First().Age);
}
[Test]
public void ShouldMapPropertyUsingAutoMapper()
{
Mapper.CreateMap<DataRow, TestClass>()
.ForMember(x => x.Name, x => x.MapFrom(y => y["firstName"]))
.ForMember(x => x.Age, x => x.MapFrom(y => y["age"]));
var dataTable = new DataTable();
dataTable.Columns.Add("firstName");
dataTable.Columns.Add("age");
for (int i = 0; i < 5000000; i++)
{
var row = dataTable.NewRow();
row["firstName"] = "John";
row["age"] = 15;
dataTable.Rows.Add(row);
}
var start = DateTime.Now;
var result = dataTable.Rows.OfType<DataRow>().Select(Mapper.Map<DataRow, TestClass>).ToList();
Console.WriteLine((DateTime.Now - start).TotalSeconds);
Assert.AreEqual("John", result.First().Name);
Assert.AreEqual(15, result.First().Age);
}
}
答案 1 :(得分:1)
可能有点像:
public class Mapping<TSource>
{
public void Assign<TResult>(TSource instance, TResult value)
{
var property = typeof(TSource).GetProperties().FirstOrDefault(p => p.PropertyType == typeof(TResult)));
if (property != null)
{
property.SetValue(instance, value, new object[0]);
}
}
}
但是您的对象需要具有每种类型的一个属性才能准确
我们甚至可以使它更通用,但更危险:
public void Assign<TResult>(TSource instance, TResult value)
{
var property = typeof(TSource).GetProperties().FirstOrDefault(p => p.PropertyType.IsAssignableFrom(typeof(TResult)));
if (property != null)
{
property.SetValue(instance, value, new object[0]);
}
}
(如果你有2个属性继承自同一个基类,这将不起作用)...