扩展 IQueryable 查询的属性

发布于 2024-09-10 18:22:24 字数 1177 浏览 2 评论 0 原文

我有以下虚构的域类:

public class Pony
{
    public string Name { get; set; }
    public DateTime FoundDate { get; set; }
}

public class Person
{
    public ICollection<Pony> Ponies { get; private set; }

    public Pony NewestPony
    {
        get
        {
            return Ponies
                .OrderBy(pony => pony.FoundDate)
                .FirstOrDefault();
        }
    }
}

NewestPony 属性封装了一个域规则,该规则确定幸运者找到的最新小马是什么。

我可以使用 Enumerable 扩展方法来使用此属性:

IEnumerable<Person> people = GetPeople();
people.Where(p => p.NewestPony != null);

这太棒了。

然而,people 是 SQL Server(本例中为 NHibernate)的一个 IQueryable 外观,查询必须由查询提供程序处理并转换为 SQL。由于数据库中没有物理“NewestPony”列,因此查询提供程序将因查询而阻塞。

为了解决这个问题,我可以扩展查询中的属性,将其替换为其 get 实现:

IEnumerable<Person> people = GetPeople();
person.Where(p 
    => p.Ponies.OrderBy(pony => pony.FoundDate).FirstOrDefault() != null);

因为查询提供程序了解 SQL Server 中的关系,所以它现在可以评估此​​查询。

该解决方案虽然可行,但确实重复了之前封装的规则。我正在寻找一种方法来避免这种重复行为,甚至可能是一种扩展查询提供程序中的属性以允许其生成正确的 SQL 语句的方法。

I have the following fictional domain classes:

public class Pony
{
    public string Name { get; set; }
    public DateTime FoundDate { get; set; }
}

public class Person
{
    public ICollection<Pony> Ponies { get; private set; }

    public Pony NewestPony
    {
        get
        {
            return Ponies
                .OrderBy(pony => pony.FoundDate)
                .FirstOrDefault();
        }
    }
}

The NewestPony property encapsulates a domain rule that determines what the newest pony that lucky person has found.

I can use this property using the Enumerable extension methods:

IEnumerable<Person> people = GetPeople();
people.Where(p => p.NewestPony != null);

This is great.

However, people is an IQueryable facade to a SQL Server (NHibernate in this case), the query has to be processed by a query provider and translated into SQL. Because there is no physical "NewestPony" column in the database, the query provider will choke on the query.

To solve this, I can expand the property in the query, replacing it with its get implementation:

IEnumerable<Person> people = GetPeople();
person.Where(p 
    => p.Ponies.OrderBy(pony => pony.FoundDate).FirstOrDefault() != null);

Because the query provider understands the relationships in SQL Server, it can evaluate this query now.

This solution, though workable, does duplicate that rule that was previously encapsulated. I'm looking for a way to avoid this duplication of behaviour, perhaps even a way to expand the property in the query provider to allow it to generate the correct SQL statements.

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

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

发布评论

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

评论(2

尝蛊 2024-09-17 18:22:24

您在这里遇到了一个典型问题。有几种方法可以解决这个问题。如果您使用某些自动映射,则应该排除该属性,如果您使用法线映射(HBM 文件),则可以忽略该属性。但是使用 LINQ 语句,正如您所解释的那样,它仍然可能会出错。以下是一些解决方法:

  1. 实用:POCO 的所有扩展都应该是方法,而不是属性。这反映了您正在做的事情:返回某些内容的操作。因此,使用 Pony GetNewestPony() 作为声明。同样,我在 POCO 中使用 GetFullName() 解决了这个问题,它结合了名字、中间名和姓氏。
  2. 设计:最好保持 POCO 简单明了。这样它们就可以自动生成并且可以在不伤害任何人的情况下进化。只是获取器/设置器,仅此而已。要解决您的问题,请执行以下操作:使用 C# 在 3.0 中引入的内容:扩展方法(不,扩展属性不存在)。
  3. 架构:通常将 DAO 层作为单独的层放置在与域层不同的组件中。这样,业务逻辑(获取选择、所有、更新、所有 CRUD 等)都可以放入其自己的 DAO 类中。要做好这一点,您需要过度使用和理解 C# 接口和泛化。这个概念由 Billy McCafferty 清晰详细地解释了,后来演变成现在的著名的 S#arp 架构

对于快速修复:使用 nr 1 或 2。对于彻底的面向未来的修复,请更改您的架构,否则 NHibernate 会在背后咬您,而不是帮助您。

编辑:
下面,adriaanp 谈到了使用扩展方法的痛点,我相信他是对的。因为我几乎总是使用自动生成的 DTO,所以我希望它们与我手动引入的任何方法完全分开。如果我希望它们在对象上,而不是作为扩展方法,Microsoft 正是出于此目的引入了 partial 类(在类本身中扩展自动生成的类,而不丢失往返工程)。但这里也一样:小心不要弄乱,清晰的文件名会有很大帮助。

You hit at a typical problem here. There are a few ways of solving this. If you use some automapping, you should exclude that property, if you use normal mapping (HBM files) you can just ignore the property. But with the LINQ statement it may still go wrong as you explained. Here are a few workarounds:

  1. Pragmatic: all your extensions to a POCO should be methods, not properties. That reflects what you are doing: an action that returns something. Hence, use Pony GetNewestPony() as declaration. Same way I solved this in POCOs with GetFullName() which combines first, middle, last name.
  2. Design: it is best to keep your POCOs plain and simple. That way they can be autogenerated and can evolve without hurting anybody. Just gettors/settors and that's it. To solve your issue then: use what C# introduced in 3.0: extension methods (no, extension properties do not exist).
  3. Architecture: it is common to place the DAO layer as a separate layer in a separate asssembly from the Domain layer. That way, both the business logic (getting selections, all, updating, all CRUD etc) can go in its own DAO class. To do this well, you need excessive use and understanding of C# interfaces and generalization. This concept is explained clearly and detailed by Billy McCafferty and later evolved into the now famous S#arp Architecture.

For a quick fix: use nr 1 or 2. For a thorough future-looking fix, change your architecture, otherwise NHibernate will bite you in the back, instead of helping you.

EDIT:
Below, adriaanp hits on a sore point of using extension methods and I believe he's right. Because I almost always use autogenerated DTO's, I want them separated completely from any methods I introduce by hand. If I want them on the object though, instead of as extension methods, Microsoft introduced partial classes for exactly this purpose (to extend auto-generated classes in the class itself, without loosing roundtrip engineering). But here too: be careful with not making it messy, clear filenames can help a lot.

往日 2024-09-17 18:22:24

这并不能完全解决您的问题,但也许会有所帮助。 使用 NHibernate 公式来辅助搜索

This won't completely solve your problem but maybe it can help. Using an NHibernate Formula to aid searching

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