我有以下虚构的域类:
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.
发布评论
评论(2)
您在这里遇到了一个典型问题。有几种方法可以解决这个问题。如果您使用某些自动映射,则应该排除该属性,如果您使用法线映射(HBM 文件),则可以忽略该属性。但是使用 LINQ 语句,正如您所解释的那样,它仍然可能会出错。以下是一些解决方法:
Pony GetNewestPony()
作为声明。同样,我在 POCO 中使用GetFullName()
解决了这个问题,它结合了名字、中间名和姓氏。对于快速修复:使用 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:
Pony GetNewestPony()
as declaration. Same way I solved this in POCOs withGetFullName()
which combines first, middle, last name.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.这并不能完全解决您的问题,但也许会有所帮助。 使用 NHibernate 公式来辅助搜索
This won't completely solve your problem but maybe it can help. Using an NHibernate Formula to aid searching