如何在运行时在IEnumerable 上创建动态多属性Select?

我昨天问了一个非常相似的问题 ,但直到今天我才意识到我接受的答案并没有解决我所有的问题。 我有以下代码:

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

使用如下:

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

这允许我从IQueryable提取主键。 问题是这段代码只适用于一个主键,我需要添加对多个PK的支持。

那么,有什么方法可以调整上面的SelectExpression方法来获取IEnumerable (这是我的主键属性名列表)并让方法返回一个选择这些键的表达式?

即给出以下内容:

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

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

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

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

您可以使用元组实现类似的结果:

 public Expression> SelectExpression(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>(body, param); return expr; } 

您可以使用Tuple<>因为必须在编译时知道匿名类型:

 public Expression> SelectExpression(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>( Expression.New(ctor, fields), param ); } 

然后:

 var primaryKeyExpression = SelectExpression("CustomerId", "OrderId"); 

将生成以下表达式:

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

正如有人已经指出的那样,你实际上是在尝试获取在运行时构造的匿名类型,这种方法不起作用。

有没有替代使用匿名类型,仍然达到我的要求?

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

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

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