2

我已经弯曲了一段时间,但我想我错过了一些东西,所以可能有人会提供帮助。

假设我有以下映射器类:

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:表达式必须是可写的
参数名称:左

4

2 回答 2

2

我已经做到了,下面的源代码。它的运行速度比 Automapper 快一些(不确定我的 Automapper 配置是否是该任务最快的),基准测试不是防弹的,但在我的机器上使用我的书面映射器映射 500 万行需要 20.16 秒,使用 Automapper 需要 39.90 秒,虽然看起来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);
    }
}
于 2012-07-18T15:02:19.627 回答
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 个从同一个基类继承的属性,这将不起作用)...

于 2012-07-18T08:47:19.187 回答