开发者

Expanding properties for an IQueryable query

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 pr开发者_如何学运维operty 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.


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜