如何在表达式树中实例化和初始化动态对象?

时间:2013-08-20 16:45:58

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

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

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

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表达式。

2 个答案:

答案 0 :(得分:3)

  

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

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

答案 1 :(得分:1)

所描述的事情很难,主要是因为我们无法在运行时动态创建匿名类型 - 它们需要在编译时才知道。所以我的主张是创建一个类,它可以包含任意选择类型的几个属性(类似于元组),但是我们将仅从对我们重要的属性加载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();
}

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