【问题标题】:How to instantiate and initialize a dynamic object in expression tree?如何在表达式树中实例化和初始化动态对象?
【发布时间】:2013-08-22 19:59:40
【问题描述】:

使用IQuerayble<TItem> 时,我们可以像这样调用Select

query.Select( item => new { A=item.Prop1, B=item.Prop2});

Select 方法需要Expression<Func<TItem,TResult>>

我需要使用ExpandoObject 而不是匿名但静态类型的类。

如果可能的话,它看起来像:

query.Select( item => dynamic new ExpandoBoject { A=item.Prop1, B=item.Prop2});

所以我想构造表达式树Expression<Func<TItem,ExpandoObject>>,其中对象的属性以与匿名类型类似的方式初始化。
动态功能仅用于初始化,因此 Func 返回 ExpandoObject 而不是 dynamic 是可以的。

我找不到太多关于 Expression.Dynamic 和我应该使用的相应活页夹的文档。


更新 1

为什么我需要所有这些东西?
因为我想get primary keys.
我想对任何实体类型都这样做。

我知道如何获取组成 PK 的属性列表,但现在我需要将实体投影到EntityKey。好吧,可能是这个类的相同等价物。

var keys = context.Set<TEntity>().Where(Expression<Func<TEntity,bool>).Select(Expression<Func<TEntity,EntityKey>>);

正如我在 cmets 中指出的,包含块的 lambda 不能转换为表达式树,所以我不能简单地创建字典并填充它。现在我正在使用语义上接近此代码的表达式树:

var dict = new Dictionary<string,object>();
dict.Add("Prop1",value1);
dict.Add("Prop2",value2);
return dict

但我怀疑 EF 能否解析包含块的表达式。需要检查。
而且我很好奇它是否可以与动态对象和 Expression.MemberInit 一起使用,因为它可以与静态对象一起使用。


更新 2

实体框架不支持字典初始化语法。
它会抛出 NotSupportedException 消息:LINQ to Entities 中仅支持具有单个元素的列表初始化项。


更新 3

EF 也不支持块表达式。
NotSupportedException 带有消息:“块”类型的未知 LINQ 表达式。

【问题讨论】:

  • 你不能做类似这样的事情:query.Select( item =&gt; dynamic new ExpandoBoject { A=item.Prop1, B=item.Prop2}); Lippert 告诉它:stackoverflow.com/questions/7478048/…
  • 我知道,我不能。这是一个打算用动态构建表达式树的例子。
  • 可以做的是在 LINQ 中创建一个匿名类型,然后在 IQueryable 完成后,在 IEnumerable 部分中将匿名类型复制到 Expando 对象。显然,这两个表达式都可以通过某种方式自动生成。
  • 现在请记住一点不同的方法。我已经实现了自己的 EntityKey 类,它实现了 IEquatable 和 IDictionary 接口。对于每个实体,我可以生成具有与 PK 相对应的属性的后代,而 PK 只需写入字典即可。但我很感兴趣是否可以避免代码生成。

标签: c# .net dynamic expression-trees iqueryable


【解决方案1】:

现在我正在使用语义上接近此代码的表达式树:

var dict = new Dictionary<string,object>();
dict.Add("Prop1",value1);
dict.Add("Prop2",value2);
return dict;

可以这样做,因为您可以像这样将代码编写为单个表达式:

new Dictionary<string, object>
{
    { "Prop1", value1 },
    { "Prop2", value2 }
};

您可以像这样创建一个包含此表达式(EF 应该能够处理)的表达式树:

var addMethod = typeof(Dictionary<string, object>).GetMethod("Add");

var expression = Expression.Lambda<Func<Dictionary<string, object>>>(
    Expression.ListInit(
        Expression.New(typeof(Dictionary<string, object>)),
        Expression.ElementInit(
            addMethod,
            Expression.Constant("Prop1"),
            value1Expression),
        Expression.ElementInit(
            addMethod,
            Expression.Constant("Prop2"),
            value2Expression)),
    itemParameterExpression);

【讨论】:

  • 我今天试试,但 EF 抱怨字典初始化语法说它只理解简单的列表。
  • 不,EF 抛出异常。
  • @voroninp 在这种情况下,您可以尝试类似new[] { Tuple.Create("Prop1", value1), Tuple.Create("Prop2", value2) }
  • 元组的类型会有所不同。不幸的是,xanto 删除了他关于 new List { ...} 的类似想法的答案
【解决方案2】:

描述的东西很困难,主要是因为我们不能在运行时动态创建匿名类型——它们需要在编译时就已经知道。所以我的建议是创建一个可以包含多个任意选择类型的属性的类(类似于元组),但是我们将从 db 值仅加载对我们重要的属性。所以我们需要这样的类:

public class CustomTuple<T1, T2>
{
    public T1 Item1 { get; set; }
    public T2 Item2 { get; set; }
}

如果我们需要更多属性,我们可能会在那里添加更多属性。如果我们有这样一个具有 5 个属性的类,使用它我们可以加载最多 5 个属性。现在投影逻辑:

Type[] parameterTypes = new Type[] { typeof(int), typeof(object) };
Type tupleType = typeof(CustomTuple<,>).MakeGenericType(parameterTypes);
ParameterExpression x = Expression.Parameter(typeof(Entity));
NewExpression body = Expression.New(tupleType.GetConstructor(new Type[0]), new Expression[0]);
MemberBinding binding1 = Expression.Bind(
    typeof(CustomTuple<,>).MakeGenericType(parameterTypes).GetProperty("Item1"),
    Expression.Property(x, "Value"));
MemberInitExpression memberInitExpression =
    Expression.MemberInit(
        body,
        binding1);

Expression<Func<Entity, object>> exp = Expression.Lambda<Func<Entity, object>>(memberInitExpression, x);
using (MyDbContext context = new MyDbContext())
{
    var list = context.Entities.Select(exp).ToList();
}

上面的代码是从Entity类Value属性的Entity集合值中选择的。 parameterTypes 定义从 Select 投影返回的参数类型。如果我们不打算使用给定的属性,那么我们将其保留为对象类型。然后我们需要构建初始化表达式。我们使用 New 方法执行此操作,我们使用 Expression.Bind 创建的表达式分配属性值,并将它们与 Expression.MemberInit 组合在一起。我们可以在运行时根据需要动态创建尽可能多的 MemberBinding 表达式。

【讨论】:

  • 其实这和我最终做的差不多。但我从零属性开始。使用允许的继承创建 ExpandoObject 的类似物,然后使用 CodeEmit 动态生成具有所需属性的祖先类。这让我可以从 EF 中通用地查询实体的 PK。
  • 动态生成这些类的好主意。我将尝试编辑我的答案以包含此内容。
猜你喜欢
  • 2012-09-23
  • 1970-01-01
  • 1970-01-01
  • 2013-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-04
  • 1970-01-01
相关资源
最近更新 更多