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

发布于 2024-12-29 03:40:04 字数 1279 浏览 3 评论 0原文

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

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 中提取主键。问题是此代码仅适用于单个主键,我需要添加对多个 PK 的支持。

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

即鉴于以下情况:

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

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

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

I asked a very similar question yesterday, but it wasn't until today I realised the answer I accepted doesn't solve all my problems. I have the following code:

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

Which is used as follows:

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

This allows me to pull out the primary keys from an IQueryable<TUnknown>. The problem is that this code only works with a single primary key and I need to add support for multiple PKs.

So, is there any way I can adapt the SelectExpression method above to take an IEnumerable<string> (which is my list of primary key property names) and have the method return an expression that selects those keys?

I.e. Given the following:

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

My Select needs to do the following (at runtime):

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

巴黎夜雨 2025-01-05 03:40:04

没有简单的方法可以完全满足您的要求,因为它需要您动态创建新类型(匿名类型是在静态已知时由编译器创建的)。虽然它当然可行,但它可能不是最简单的选择...

您可以使用 元组

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

There is no easy way to do exactly what you want, because it would require you to create a new type dynamically (anonymous types are created by the compiler when they're known statically). While it is certainly feasible, it's probably not the easiest option...

You can achieve a similar result using 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;
}
明月夜 2025-01-05 03:40:04

您可以使用 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)

You could use Tuple<> because anonymous types must be known at compile time:

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

and then:

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

will generate the following expression:

item => new Tuple<string, string>(item.CustomerId, item.OrderId)
伴梦长久 2025-01-05 03:40:04

正如有人已经指出的那样,您本质上是在尝试获取在运行时构造的匿名类型,这是行不通的。

除了使用匿名类型之外,还有其他方法可以实现我的要求吗?

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

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

在我们公司,我们正是这样做的,受到 SubSonic 的启发,但发现 SubSonic 本身并不完全是我们想要的。在我看来,效果相当好。

As someone has already pointed out, you are essentially trying to get an anonymous type constructed at runtime, which is not going to work.

Is there an alternative to using an Anonymous type and still achieve what I require?

This really depends on what you mean. The obvious answers are to use runtime constructs, such as a dictionary, a tuple, etc. But you are probably fully aware of this yourself, so I assume that you want a compile-time-typed result with real field names, so that any mis-use of primaryKeys gets caught during compilation.

If so, then I'm afraid your only option is to generate the relevant code before compilation. This isn't as bad as it might sound, but is not completely transparent: when you change the schema, you have to re-run the code generation somehow.

At our company we've done exactly that, being inspired by SubSonic but finding that SubSonic itself wasn't quite what we wanted. It worked out rather well in my opinion.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文