2012-07-18 65 views
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"); 

而且finaly一些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

如果我理解正確,你想分配爲分配和映射=>映射? – Kek 2012-07-18 08:37:57

+0

我不明白你在問什麼。 – 2012-07-18 08:40:10

+1

也許[AutoMapper的QueryableExtensions](https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/QueryableExtensions.cs)的方法會激勵你。他們使用兩階段投影來保持兩個通用參數不同。 – 2012-07-18 08:53:56

回答

2

我已經設法做到了,下面的源代碼。它的運行速度比Automapper快一些(不知道我的Automapper配置是否是這項任務中速度最快的),但基準測試並非無懈可擊,但在我的機器上映射了500萬行,使用我的寫入映射器需要20.16秒,使用Automapper需要39.90秒, Automapper使用較少的內存來執行此任務(尚未測量它,但Automapper提供了1000萬行結果,而我的映射程序使用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); 
    } 
} 
+0

擺脫'TResult'不是很好。你看到你依賴於'Delegate.DynamicInvoke'。這會傷害。在那裏度過了20秒的好時光。 – nawfal 2014-01-01 02:42:45

1

喜歡的東西THID也許:

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]); 
     } 

    } 
} 

但你的對象需要有一個PROPERT每種類型的這種Y的是準確

我們甚至可以使它更通用的,但更危險:

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個場所中,相同的基本繼承這是不行的類)...

+0

這可以工作,只是我想我需要緩存propertyinfo的某處,以避免反射性能打擊。從另一方面,我希望看到使用表達式的解決方案:) – Giedrius 2012-07-18 09:02:43

+0

是的,我認爲我不知道你的要求,配置作爲一個單獨的類... – Kek 2012-07-18 09:03:36