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

发布于 2024-12-18 11:29:05 字数 1271 浏览 2 评论 0原文

因此,我正在使用旧的数据模型,并且我必须在我所掌握的范围内工作。

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

List<Dictionary<string, object>>

每个字典的“Where”返回,键是列名,值是列值。正如你可以想象的,使用它是 foreach 循环和类型转换的噩梦,

我希望定义一些 POCO 视图模型,然后制作一些使用 LINQ/反射的东西,以及一个“赋值绑定映射”,从可怕的返回值变为我漂亮干净的POCO。所以我可以用列名和 lambda 定义 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;

这可以使用某种反射来完成吗?如果可以,如何实现?

So I'm working with an old data model, and I kind of have to work within what I've been handed.

When I perform a database query, the model returns data as a

List<Dictionary<string, object>>

Where for each dictionary, the key is the column name and the value is the column value. As you can imagine, working with this is a nightmare of foreach loops and type casting

I'm hoping to define some POCO viewmodels and then making something that uses LINQ/reflection, and an "assignment binding map" to go from hideous return value to my nice clean POCO. So I could define "maps" with the column names and lambdas to the properties on my POCO, similar to this...

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

Then convert like this...

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;

Can this be done using some sort of reflection, and if so, how?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

笔芯 2024-12-25 11:29:05

这是使用表达式树的方法。首先,定义地图的 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 因为它不带参数并返回映射类型的对象。

然后,我们编译它。此调用生成一个带有我们可以调用的签名 Func 的方法,并且该方法的主体是我们创建为数据结构的代码。这是一种无需直接使用反射即可进行反射的巧妙方法。

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

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

    return initializationFunction();
}

Here is an approach using expression trees. First, define the API of the map:

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

    public T CreateObject(IDictionary<string, object> values);
}

You would use it like this:

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

To implement it, first you would declare a dictionary to associate names from the data source to properties:

private readonly IDictionary<string, PropertyInfo> _properties = new Dictionary<string, PropertyInfo>();

Next, you would implement the Add method in terms of that dictionary (all error handling left as an exercise for the reader):

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

Then, you would dynamically compile a method, using expression trees, which does the assignments (it sounds scarier than it is). The easiest way to visualize this process is to look at an example of what we're building. What we want is some code which does this:

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

But, we can't know that at compile-time because of the dynamic mappings. So, we'll build a function which is that exact code, but compiled at runtime. Expression trees are just runtime data that represents the same code you could write at compile-time.

First, we need to get a set of bindings (assignments) for the properties:

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

What we're saying here is, for each property in the mapped properties, look up the value and make it a constant (for Id, this might be the value 7) and bind the corresponding property to it. This gives us the expression Id = 7. We repeat this for all of the properties, giving us all of the assignments.

Once we have those bindings, we can create the full member initialization, which includes the constructor call:

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

Because we specified where T : new() in the class declaration, we are guaranteed to have a parameterless constructor to call here. We pass in the property bindings we created before, giving us a data structure that represents the initialization expression we wanted to build.

So what do we do know? We have this data structure, but how do we call the code? To do that, we have to wrap that expression in a function that we can call, because the only thing you can actually invoke is a method. This means we are really building code that looks like this:

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

That is a parameterless function which, when invoked, will return the initialized object. This is also called a lambda expression. We can get the data structure for this like so:

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

    return initializationLambda.Compile();
}

We create a lambda expression whose body is the member initialization, which is exactly the code we wrote above. We specify the delegate type Func<T> because it takes no parameters and returns an object of the mapped type.

Then, we compile it. This call generates a method with the signature Func<T> that we can call, and which has as its body the code we created as a data structure. This is a neat way of doing reflection without using reflection directly.

Finally, we implement the CreateObject method we defined earlier by creating the function and invoking it, giving us an instance of T (ProductViewModel here):

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

    return initializationFunction();
}
等你爱我 2024-12-25 11:29:05

您可以做的是从 p =>; 类型的 linq 表达式中提取属性名称。 p.Id 使用类似这样的东西

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"
}

(没有编译代码;关于反射部分,网上有很多文章。希望这有帮助)

What you could do is to extract the property name from a linq expression of the kind p => p.Id using something like this

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

..and then use plain old reflection to actually assign the value to the object instance. For instance create a method

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"
}

(didn't compile the code; for the reflection part, there are numerous articles on the web. Hope this helps)

痞味浪人 2024-12-25 11:29:05

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

This seems like a perfect match for dynamic . You can create a dynamic class that has the properties based on the keys in the dictionary. Check the DynamicObject class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文