赋值表达式列表

时间:2012-07-18 08:35:24

标签: c# linq c#-4.0

我已经弯曲了一段时间,但我想我错过了什么,所以可能有人会帮忙。

我们说我有以下映射器类:

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

2 个答案:

答案 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个属性继承自同一个基类,这将不起作用)...