【问题标题】:How can I create a dynamic multi-property Select on an IEnumerable<T> at runtime?如何在运行时在 IEnumerable<T> 上创建动态多属性选择?
【发布时间】:2012-01-25 10:01:11
【问题描述】:

我昨天问了very similar question,但直到今天我才意识到我接受的答案并不能解决我所有的问题。我有以下代码:

public Expression<Func<TItem, object>> SelectExpression<TItem>(string fieldName)
{
    var param = Expression.Parameter(typeof(TItem), "item");
    var field = Expression.Property(param, fieldName);
    return Expression.Lambda<Func<TItem, object>>(field, 
        new ParameterExpression[] { param });
}

具体用法如下:

string primaryKey = _map.GetPrimaryKeys(typeof(TOriginator)).Single();
var primaryKeyExpression = SelectExpression<TOriginator>(primaryKey);
var primaryKeyResults = query.Select(primaryKeyExpression).ToList();

这允许我从IQueryable&lt;TUnknown&gt; 中提取主键。问题是此代码仅适用于单个主键,我需要添加对多个 PK 的支持。

那么,我有什么办法可以调整上面的 SelectExpression 方法以采用 IEnumerable&lt;string&gt;(这是我的主键属性名称列表)并让该方法返回一个选择这些键的表达式?

即鉴于以下情况:

var knownRuntimePrimaryKeys = new string[] { "CustomerId", "OrderId" }`

我的 Select 需要执行以下操作(在运行时):

var primaryKeys = query.Select(x=> new { x.CustomerId, x.OrderId });

【问题讨论】:

  • 问题是作为select statemet结果的匿名类型是在编译时创建的..
  • 除了使用 Anonymous 类型之外,还有其他方法可以达到我的要求吗?还是回到绘图板上?
  • @GenericTypeTea,元组对你来说是一个可以接受的选项吗?
  • @ThomasLevesque 是的,我不明白为什么不这样做。你想到了什么?
  • @GenericTypeTea,我会在几分钟后发布答案

标签: c# linq lambda duck-typing


【解决方案1】:

没有简单的方法可以完全按照您的意愿进行操作,因为它需要您动态创建一个新类型(匿名类型在静态已知时由编译器创建)。虽然这当然是可行的,但它可能不是最简单的选择......

您可以使用tuples 获得类似的结果:

public Expression<Func<TItem, object>> SelectExpression<TItem>(string[] propertyNames)
{
    var properties = propertyNames.Select(name => typeof(TItem).GetProperty(name)).ToArray();
    var propertyTypes = properties.Select(p => p.PropertyType).ToArray();
    var tupleTypeDefinition = typeof(Tuple).Assembly.GetType("System.Tuple`" + properties.Length);
    var tupleType = tupleTypeDefinition.MakeGenericType(propertyTypes);
    var constructor = tupleType.GetConstructor(propertyTypes);
    var param = Expression.Parameter(typeof(TItem), "item");
    var body = Expression.New(constructor, properties.Select(p => Expression.Property(param, p)));
    var expr = Expression.Lambda<Func<TItem, object>>(body, param);
    return expr;
}

【讨论】:

  • 不幸的是,这似乎不适用于实体框架中的 IQueryable。 “LINQ to Entities 中仅支持无参数构造函数和初始化程序。”
  • @GenericTypeTea 也许很明显,但我在使用前通过编译表达式解决了这个错误消息,即:var primaryKeyResults = query.Select(primaryKeyExpression.Compile()).ToList();
  • @Jorr.it 您的评论可能是真的,但查询不会在数据库上执行
【解决方案2】:

您可以使用Tuple&lt;&gt;,因为在编译时必须知道匿名类型:

public Expression<Func<TItem, object>> SelectExpression<TItem>(params string[] fieldNames)
{
    var param = Expression.Parameter(typeof(TItem), "item");
    var fields = fieldNames.Select(x => Expression.Property(param, x)).ToArray();
    var types = fields.Select(x => x.Type).ToArray();
    var type = Type.GetType("System.Tuple`" + fields.Count() + ", mscorlib", true);
    var tuple = type.MakeGenericType(types);
    var ctor = tuple.GetConstructor(types);
    return Expression.Lambda<Func<TItem, object>>(
        Expression.New(ctor, fields), 
        param
    );
}

然后:

var primaryKeyExpression = SelectExpression<TOriginator>("CustomerId", "OrderId");

将生成以下表达式:

item => new Tuple<string, string>(item.CustomerId, item.OrderId)

【讨论】:

  • 不幸的是,这似乎不适用于实体框架中的 IQueryable。 “LINQ to Entities 中仅支持无参数构造函数和初始化程序。”
  • @GenericTypeTea,哦,好吧,这很可悲。在这种情况下,您可以使用 Reflection.Emit 在运行时生成具有正确数量属性的动态类型,然后构建分配这些属性的表达式。不过看起来工作量很大。
  • 我开始怀疑是否最好只强制使用应用程序实现我在其所有 EF POCO 对象上指定的接口。然后我可以让他们实现一个 GetPrimaryKeySelectExpression 方法......但这会导致各种可能的问题(不包括它破坏了 POCO 类的整个原则的事实)以及在 b' 中完全痛苦后供用户实施。秋千和环形交叉路口!
  • @GenericTypeTea 也许很明显,但我在使用前通过编译表达式解决了这个错误消息,即:var primaryKeyResults = query.Select(primaryKeyExpression.Compile()).ToList();
  • 如果TItem 未知怎么办?在我的场景中,我想在TItem 中加载属性,其中这些属性可以是我们在编译时未知的层次结构图中的各种子类。我们可以在附加 TItem 的属性的同时构建这个函数,这些属性只存在于它的子类中(在 EF 中使用)?
【解决方案3】:

正如有人已经指出的那样,您实际上是在尝试在运行时构造一个匿名类型,但这是行不通的。

除了使用 Anonymous 类型,还有其他方法可以达到我的要求吗?

这真的取决于你的意思。显而易见的答案是使用运行时构造,例如字典、元组等。但是您自己可能完全意识到这一点,所以我假设您想要一个带有真实字段名称的编译时类型的结果,这样任何在编译过程中会发现滥用primaryKeys

如果是这样,那么恐怕您唯一的选择就是在编译之前生成相关代码。这并不像听起来那么糟糕,但并不完全透明:当您更改架构时,您必须以某种方式重新运行代码生成。

在我们公司,我们确实做到了这一点,受到SubSonic 的启发,但发现 SubSonic 本身并不是我们想要的。在我看来,效果相当不错。

【讨论】:

  • 我对运行时的主键一无所知。上面的代码由我无法控制的消费应用程序使用。请参阅上一个问题以获得更好的解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 2010-09-11
  • 1970-01-01
  • 2012-08-13
相关资源
最近更新 更多