views:

56

answers:

2

I have a the following 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.

+1  A: 

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.

Abel
I would be very careful of #2. You could end up with lots and lots of extension methods that could turn into a maintenance nightmare. POCOs should contain behavior as well in my opinion they're not just DTO's.
adriaanp
1 it will still attempt to convert them into SQL statements.
Programming Hero
@Programming Hero: I've never seen that happening, esp. not with extension methods since they are by no means part of the class itself. I basically use approach nr 3 and sometimes cheat with extensions methods (without making it messy, but they often end up in the more stable classes later on anyway) and don't hit the problem you describe. Maybe there's something inherently different in your design that I'm missing here?
Abel
@Abel: It's my understanding that a query provider will receive a `MethodCallExpression` for the methods in the query. It then has to attempt to evaluate and express that method call as an appropriate SQL statement, which is the point it chokes on a derived property.
Programming Hero
+1  A: 

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

adriaanp
It wont directly solve my immediate problem, but I can see this being very useful for simpler scenarios.
Programming Hero