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



所以我正在使用一个旧的数据模型,我不得不在我已经交给的范围内工作。

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

List<Dictionary<string, object>>

其中对于每个字典,键是列名,值是列值。可以想象,使用它是foreach循环和类型强制转换

的噩梦。

我希望定义一些POCO视图模型,然后使用LINQ/反射制作一些东西,以及一个"赋值绑定映射",从可怕的返回值到我干净的POCO。所以我可以定义"映射"与列名和lambdas的属性在我的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);

然后像这样转换…

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;

这可以用某种反射来完成吗?如果可以,怎么做?

这是一个使用表达式树的方法。首先,定义映射的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);
}

你可以这样使用:

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

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

private readonly IDictionary<string, PropertyInfo> _properties = new Dictionary<string, PropertyInfo>();
接下来,您将根据该字典实现Add方法(所有错误处理留给读者作为练习):
public void Add(string sourceName, Expression<Func<T, object>> getProperty)
{
    _properties[sourceName] = (PropertyInfo) ((MemberExpression) getProperty.Body).Member;
}

然后,您将使用表达式树动态编译一个方法,它执行赋值(听起来比实际更可怕)。可视化这个过程的最简单方法是看一个我们正在构建的例子。我们需要的是这样的代码:

new ProductViewModel
{
    Id = ...,
    Code = ...,
    Description = ...,
    Active = ...
}

但是,由于动态映射,我们无法在编译时知道。我们将构建一个函数它就是那个代码,但是在运行时编译。表达式树只是运行时数据,表示你可以在编译时编写的相同代码。

首先,我们需要为属性获取一组绑定(赋值):
private IEnumerable<MemberBinding> GetPropertyBindings(IDictionary<string, object> values)
{
    return
        from sourceName in _properties.Keys
        select Expression.Bind(_properties[sourceName], Expression.Constant(values[sourceName]));
}

我们在这里说的是,对于映射属性中的每个属性,查找值并使其成为常量(对于Id,这可能是值7)并将相应的属性绑定到它。这就得到了表达式Id = 7。我们对所有的属性重复这个过程,得到所有的赋值。

一旦有了这些绑定,就可以创建完整的成员初始化,其中包括构造函数调用:
private MemberInitExpression GetMemberInit(IDictionary<string, object> values)
{
    return Expression.MemberInit(Expression.New(typeof(T)), GetPropertyBindings(values));
}

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

我们知道什么?我们有了这样的数据结构,但是我们如何调用代码呢?要做到这一点,我们必须将该表达式包装在一个可以调用的函数中,因为您实际上可以调用的唯一东西是方法。这意味着我们实际上是在构建这样的代码:

() => new ProductViewModel
{
    Id = ...,
    Code = ...,
    Description = ...,
    Active = ...
}

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

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

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

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

最后,我们通过创建函数并调用它来实现前面定义的CreateObject方法,给我们一个T的实例(这里是ProductViewModel):
public T CreateObject(IDictionary<string, object> values)
{
    var initializationFunction = GetInitializationFunction(values);
    return initializationFunction();
}

您可以做的是从p => p.Id类型的linq表达式中提取属性名称,使用如下所示

public static string GetPropertyName<T>(Expression<Func<T>> expression)
{
    MemberExpression body = (MemberExpression)expression.Body;
    return body.Member.Name;
}

. .然后使用普通的反射将值赋值给对象实例。例如,创建一个方法

private void Assign(object objInstance, Expression<Func<T>> propertyExpression, object value)
{
    string propertyNameToAssign = GetPropertyName(propertyExpression);
    //TODO use reflection to assign "value" to the property "propertyNameToAssign" of "objInstance"
}

(未编译代码;关于反思部分,网上有很多文章。

这似乎是dynamic的完美匹配。您可以创建一个动态类,该类具有基于字典中的键的属性。查看DynamicObject

相关内容

  • 没有找到相关文章

最新更新