在 NHibernate 中重写 Linq 表达式 + DDD 上下文

发布于 2024-12-01 09:28:13 字数 2122 浏览 1 评论 0原文

我有一个关于转换 Linq 表达式的棘手问题。我进行了很好的搜索,但找不到任何似乎涵盖此案的内容。我对 Linq 相当熟悉,至少在创建 lambda 并将其传递给方法方面如此,但我对表达式的东西有点弱。

首先,一些背景信息:我有一个基于 NHibernate 的通用持久性解决方案,在许多 DDD 项目中使用。对于聚合中给定的子集合本质上可能是无限的(即确实非常大)的特殊情况,我不能简单地映射集合或包,因为将整个集合加载到内存中是永远不可接受的。在此架构中,使用 API 的代码不可能直接与存储库对话以执行查询或以这种方式限制结果。我当然可以在 API 中根本没有集合,而是公开方法来检索子对象的相关子集(如果这不起作用,那就是我要做的),但我正在尝试做一些稍微不同的事情我几乎已经开始工作了...

这些项目正在使用 Fluent 进行映射(不是自动映射),因此我以以下形式向我的基本映射类添加了一个方法

HasManyQueryable<TCollection>(Expression<Func<T, IQueryable<TCollection>>> memberExpression, Expression<Func<T, TCollection, bool>> selector)

此方法派生了相关的 PropertyInfo 来自第一个表达式(指定要映射的成员)。选择器 Expression 包含父对象和子对象之间的关系,作为普通 NHibernate 映射的替代。

因此,假设我在映射中针对域类型 User (即上面的 T)有一个选择器:

HasManyQueryable<Transaction>(x => x.Transactions, (u, t) => t.User == u);

这指定了所有 Transactions< 的子集之间的映射/code> 其中 Transaction.User 是提供的 User uUser.Transactions 属性是 <代码>IQueryable<事务>。当构造实际的 User 对象时,我需要将其转换为

Expression<Func<Transaction, bool>> expression = (t => t.User == this)

this 是正在构造的 User 对象。换句话说,我想采用一条通用规则,说明如何将Users映射到Transactions,并将其转换为有关映射this的规则<代码>用户到交易。然后,我可以使用此表达式通过执行 Linq 查询从存储库生成 IQueryable,因此:

return Repository.For<Transaction>().Where(selector);

这仅在选择器为 FuncFunc时才有效。 code>,因此我的最终需要是将原始表达式(它将生成 Func)转换为 Func

这给了我一个 IQueryable 集合,其中所有查询操作都作为 Linq-to-NHibernate 查询完成,因此整个集合永远不会加载到内存中(是的,我知道您可以构建一个查询,该查询将实际上让它做到了这一点,但我可以在代码审查时捕获它们)。

唷。希望这是有道理的。

任何具有 leet Expression 重写技能的人都能够为我指明正确的方向吗?

I have a thorny question about transforming Linq Expressions. I had a good search about, but I couldn't find anything that seems to cover this case. I'm reasonably familiar with Linq, at least in terms of creating and passing lambdas to methods, but I'm somewhat weaker on the Expression stuff.

First, some context: I have a generic persistence solution based on NHibernate used inside a number of DDD-ish projects. For special cases where a given collection of children within an aggregate could be essentially infinite (ie very large indeed), I cannot simply map a set or bag as it would never be acceptable for the entire collection to be loaded into memory. In this architecture it's not possible for code consuming the API to talk to the Repository directly to do a query or limit the results that way. I could of course not have a collection in the API at all and expose methods to retrieve relevant subsets of child objects instead (and if this doesn't work that's what I'll do), but I'm trying to do something slightly different and I've almost got it working...

These projects are being mapped with Fluent (not auto-mapping), and so I added a method to my base map class in the form

HasManyQueryable<TCollection>(Expression<Func<T, IQueryable<TCollection>>> memberExpression, Expression<Func<T, TCollection, bool>> selector)

This method derives the relevant PropertyInfo from the first Expression (which specifies the member to map). The selector Expression contains the relationship between the parent and child objects as a substitute for a normal NHibernate mapping.

So, suppose I have a selector in the map for a domain type User (which is T above):

HasManyQueryable<Transaction>(x => x.Transactions, (u, t) => t.User == u);

This specifies a mapping between the subset of all Transactions where Transaction.User is the supplied User u, and the User.Transactions property which is IQueryable<Transaction>. When an actual User object is constructed, I need to turn this into

Expression<Func<Transaction, bool>> expression = (t => t.User == this)

where this is the User object being constructed. In other words, I want to take a general rule that says how to map Users to Transactions and turn it into a rule about mapping this User to Transactions. I can then use this expression to generate an IQueryable<Transaction> from the Repository by doing a Linq query, thus:

return Repository.For<Transaction>().Where(selector);

This can only work when the selector is Func<Transaction, bool>, hence my ultimate need is to turn the original expression, which would generate a Func<User, Transaction, bool> into Func<Transaction, bool>.

This gives me an IQueryable collection where all the query operations are being done as Linq-to-NHibernate queries and thus the entire collection never gets loaded into memory (yes, I know you could frame a query that would actually make it do that, but I can catch those at code review time).

Phew. Hope that makes sense.

Anyone with leet Expression re-writing skills able to point me in the right direction?

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

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

发布评论

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

评论(1

彩扇题诗 2024-12-08 09:28:13

老实说,我对您从领域角度尝试做的事情感到有点迷失。但从代码的角度来看,听起来您所需要的只是一个柯里化函数——一种接受 User 并生成 Func 的方法。一个示例方法是这样的,尽管您可以内联执行相同的操作:

Func<Transaction,bool> UserSpecificSelector(User user,
                                            Func<User,Transaction,bool> selector)
{
    return t => selector(user, t);
}

因此,要获取特定于用户的选择器,您会做...

Func<User, Transaction, bool> selector = // whatever;
User user = //whatever;
Repository.For<Transaction>().Where(t => selector(user, t));

我怀疑您需要直接使用 Expression 层次结构,除非您正在实现自己的查询提供程序,但在本例中并非如此。

I'm honestly kind of lost about what you're trying to do from a domain perspective. But from a code perspective, it sounds like all you need is a curried function--a method that takes a User and produces a Func. An example method would be this, though you could do the same thing inline:

Func<Transaction,bool> UserSpecificSelector(User user,
                                            Func<User,Transaction,bool> selector)
{
    return t => selector(user, t);
}

So to get the user-specific selector, you would do...

Func<User, Transaction, bool> selector = // whatever;
User user = //whatever;
Repository.For<Transaction>().Where(t => selector(user, t));

I doubt you need to work directly with the Expression hierarchy unless you're implementing your own query provider, which you aren't in this case.

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