使用LINQ表达式分配给对象的属性

Ste*_*man 9 c# linq reflection

所以我正在使用一个旧的数据模型,我必须在我所处理的范围内工作.

当我执行数据库查询时,模型将数据作为a返回

List<Dictionary<string, object>>
Run Code Online (Sandbox Code Playgroud)

每个字典的位置,键是列名,值是列值.你可以想象,使用它是foreach循环和类型转换的噩梦

我希望定义一些POCO视图模型然后制作一些使用LINQ /反射的东西,以及一个"赋值绑定贴图",从可怕的返回值转到我漂亮的干净POCO.所以我可以使用列名和lambda来定义"maps"到我的POCO上的属性,类似于这个......

var Map; // type???
Map.Add("Id", p => p.Id);
Map.Add("Code", p => p.Code);
Map.Add("Description", p => p.Description);
Map.Add("Active", p => p.Active);
Run Code Online (Sandbox Code Playgroud)

然后像这样转换......

List<Dictionary<string, object>> Results = MyModel.Query(...);
List<ProductViewModel> POCOs = new List<ProductViewModel>();

foreach (var Result in Results) // Foreach row
{
  ProductViewModel POCO = new ProductViewModel();

  foreach (var i in Result) // Foreach column in this row
  {
    // This is where I need help.
    // i.Key is the string name of my column.
    // I can get the lambda for this property from my map using this column name.
    // For example, need to assign to POCO.Id using the lambda expression p => p.Id
    // Or, assign to POCO.Code using the lambda expression p => p.Code
  }

  POCOs.Add(POCO);
}

return POCOs;
Run Code Online (Sandbox Code Playgroud)

这可以通过某种反射来完成,如果是这样,怎么做?

Bry*_*tts 17

这是一种使用表达式树的方法.首先,定义地图的API:

public class PropertyMap<T> where T : new()
{
    public void Add(string sourceName, Expression<Func<T, object>> getProperty);

    public T CreateObject(IDictionary<string, object> values);
}
Run Code Online (Sandbox Code Playgroud)

你会像这样使用它:

var map = new PropertyMap<ProductViewModel>();

map.Add("Id", p => p.Id);
map.Add("Code", p => p.Code);
map.Add("Description", p => p.Description);
map.Add("Active", p => p.Active);

var productViewModel = map.CreateObject(values);
Run Code Online (Sandbox Code Playgroud)

要实现它,首先要声明一个字典,以将数据源中的名称与属性相关联:

private readonly IDictionary<string, PropertyInfo> _properties = new Dictionary<string, PropertyInfo>();
Run Code Online (Sandbox Code Playgroud)

接下来,您将Add根据该字典实现该方法(所有错误处理留作读者的练习):

public void Add(string sourceName, Expression<Func<T, object>> getProperty)
{
    _properties[sourceName] = (PropertyInfo) ((MemberExpression) getProperty.Body).Member;
}
Run Code Online (Sandbox Code Playgroud)

然后,您将动态编译一个方法,使用表达式树来完成分配(听起来比它更可怕).可视化此过程的最简单方法是查看我们正在构建的示例.我们想要的是一些执行此操作的代码:

new ProductViewModel
{
    Id = ...,
    Code = ...,
    Description = ...,
    Active = ...
}
Run Code Online (Sandbox Code Playgroud)

但是,由于动态映射,我们无法在编译时知道这一点.因此,我们将构建一个功能,它是确切的代码,但在运行时编译.表达式树只是运行时数据,表示您可以在编译时编写的相同代码.

首先,我们需要为属性获取一组绑定(赋值):

private IEnumerable<MemberBinding> GetPropertyBindings(IDictionary<string, object> values)
{
    return
        from sourceName in _properties.Keys
        select Expression.Bind(_properties[sourceName], Expression.Constant(values[sourceName]));
}
Run Code Online (Sandbox Code Playgroud)

我们在这里说的是,对于映射属性中的每个属性,查找该值并使其成为常量(对于Id,这可能是值7)并将相应的属性绑定到它.这给了我们表达Id = 7.我们对所有属性重复此操作,为我们提供所有分配.

一旦我们有了这些绑定,我们就可以创建完整的成员初始化,其中包括构造函数调用:

private MemberInitExpression GetMemberInit(IDictionary<string, object> values)
{
    return Expression.MemberInit(Expression.New(typeof(T)), GetPropertyBindings(values));
}
Run Code Online (Sandbox Code Playgroud)

因为我们where T : new()在类声明中指定,所以我们保证在这里调用无参数构造函数.我们传入之前创建的属性绑定,为我们提供了一个表示我们想要构建的初始化表达式的数据结构.

那么我们知道什么?我们有这种数据结构,但我们如何调用代码?为此,我们必须将该表达式包装在我们可以调用的函数中,因为您实际可以实际调用的唯一方法是方法.这意味着我们真正构建的代码如下所示:

() => new ProductViewModel
{
    Id = ...,
    Code = ...,
    Description = ...,
    Active = ...
}
Run Code Online (Sandbox Code Playgroud)

这是一个无参数函数,当被调用时,它将返回初始化对象.这也称为lambda表达式.我们可以像这样得到数据结构:

private Func<T> GetInitializationFunction(IDictionary<string, object> values)
{
    var initializationLambda = Expression.Lambda<Func<T>>(GetMemberInit(values));

    return initializationLambda.Compile();
}
Run Code Online (Sandbox Code Playgroud)

我们创建一个lambda表达式,其主体是成员初始化,这正是我们上面编写的代码.我们指定委托类型,Func<T>因为它不接受任何参数并返回映射类型的对象.

然后,我们编译它.这个调用生成一个带有Func<T>我们可以调用的签名的方法,并且它的主体是我们创建的代码作为数据结构.这是一种不使用反射直接进行反射的简洁方法.

最后,我们CreateObject通过创建函数并调用它来实现我们之前定义的方法,给我们一个T(ProductViewModel这里)的实例:

public T CreateObject(IDictionary<string, object> values)
{
    var initializationFunction = GetInitializationFunction(values);

    return initializationFunction();
}
Run Code Online (Sandbox Code Playgroud)

  • @Baconcheese:这是正确的 - 一旦你有了'Func <T>`,就好像你已经传入了你自己编写的方法的引用.在这里也有充足的缓存机会; 例如,您可能只在第一次和添加映射后生成方法.然后,如果在应用程序级别缓存`PropertyMap <T>`实例,则会产生很少的开销. (2认同)