昨天我问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<TUnknown>
中提取主键。问题是这段代码只适用于一个主键,我需要添加对多个PK的支持。
那么,有什么方法可以调整上面的SelectExpression
方法来获取IEnumerable<string>
(这是我的主键属性名称列表)并让方法返回一个选择这些键的表达式?
即。鉴于以下内容:
var knownRuntimePrimaryKeys = new string[] { "CustomerId", "OrderId" }`
My Select需要执行以下操作(在运行时):
var primaryKeys = query.Select(x=> new { x.CustomerId, x.OrderId });
答案 0 :(得分:3)
没有简单的方法可以完全按照您的要求进行操作,因为它需要您动态创建新类型(编译器在静态知道时会创建匿名类型)。虽然这当然是可行的,但它可能不是最简单的选择......
您可以使用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;
}
答案 1 :(得分:2)
您可以使用Tuple<>
,因为必须在编译时知道匿名类型:
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)
答案 2 :(得分:1)
正如有人已经指出的那样,你实际上是在尝试获取在运行时构造的匿名类型,这种方法不起作用。
是否有替代使用匿名类型并仍能达到我的要求?
这实际上取决于你的意思。显而易见的答案是使用运行时构造,例如字典,元组等。但是你可能自己完全了解这一点,所以我假设你需要一个带有真实字段名的编译时类型的结果,所以任何编译期间会误用primaryKeys
。
如果是这样,那么我担心您唯一的选择是在编译之前生成相关代码。这并不像听起来那么糟糕,但并不完全透明:当您更改架构时,您必须以某种方式重新运行代码生成。
在我们公司,我们做到了这一点,受到SubSonic的启发,但发现SubSonic本身并不是我们想要的。在我看来,它的效果相当不错。