Wednesday, July 16, 2008

Advanced Domain Model queries using Linq - Part II

Since my previous post I've rewritten the internals to make use of LinqKit - a fantastic lightweight project.

See the changes

I took my rewrite as an opportunity to support the full query syntax:
var overdue = from a in repository.AsQueryable()
where a.IsOverdue == true
select a.OverdueFee;

var days = repository.AsQueryable()
.Where(x => x.IsOverdue)
.Select(x => x.DaysOverdue);

and added easy support for compiled queries (currently only supporting linq to objects and linq to sql):
var query = repository.Compile(
source => from x in source
where x.IsOverdue == true
select x.OverdueFee);

var results = repository.Execute(query);

I've also optimized the expression parsing (speed difference is negligible to normal queries), and I tweaked the QueryProperty construction:
#region IsOverdue Property

public bool IsOverdue
{
get { return IsOverdueProperty.GetValue(this); }
}

static public readonly QueryProperty<Loan, bool> IsOverdueProperty = new QueryProperty<Loan, bool>(
x => x.IsOverdue,
x => x.DateReturned == null && (DateTime.Now - x.DateBorrowed).TotalDays > x.LoanPeriod);

#endregion

The big change here is that I've added an extra parameter; a reference back to the mapping property.

I'd really like a memberof(Loan.IsOverdue) keyword style syntax in C#, but since there's not, I use this expression to grab the MemberInfo - which is used in the expression parser.

It's a slight DRY drawback, but it's a faster way than using reflection (and avoids an ugly string lookup) - and it's much faster than using attributes.

Get the code

Most of the big changes are all implementation specific, so be sure to check out the sample app.

Please let me know what you think!

BTW: Joseph, I made some architectural changes to the LinqKit.ExpandableQuery stuff.  Externally it behaves exactly the same, but it now lets me reuse a lot more code.

It's light on the comments, but I'm hoping my next post will explain myself.


ed: Continue to Part III

6 comments:

Nick said...

Wow love how this is shaping up! :)

Have you considered deriving specific query properties from QueryProperty<T>, so that perhaps the property could be attributed with its query implementation (similar to what Fredrik K was doing) ??

:) keep it coming dude!

Nick said...

e.g.:

class IsOverdueProperty : QueryProperty<Loan, bool>
{
public IsOverdueProperty()
: base(
x => x.IsOverdue,
x => x.DateReturned == null && (DateTime.Now - x.DateBorrowed).TotalDays > x.LoanPeriod))
{}
}

then

[IsOverdueProperty]
public bool IsOverdue
{
get
{
return new IsOverdueProperty().GetValue(this);
}

(in this version, imagining that QueryProperty<T> derives from Attribute)

Nick said...

Oops - the x => x.IsOverdue is unnecessary in the base() call :)

Luke Marshall said...

Thanks Nick!

Usage is always a tough one!

I briefly played with Attributes, but found them to be too slow and awkward (they don't support generics!)

However, since your comments, I had another look - and I think I've cracked it.

It's still slower than my previous code (though not by much!), but it might look prettier - depending on your taste.

Look out for my next post!

Keith J. Farmer said...

The problem I have with Nick's attribute approach is that you're creating a new type for a one-off decoration. Littering the type system like this -- tightly-coupled one-offs -- is a pretty foul smell in my book.

Having the static property isn't a sin, as you probably know from WPF (where I suspect you've taken this pattern from). However, I'd suggest making it private, and (like WPF) instantiate it via a factory that performs registration so you don't have to worry about public/private access. In this way, you should be able to encapsulate it completely and keep your API nice and clean.

Luke Marshall said...

Thanks Keith! I like your suggestion, particularly the factory registration support. (and yes WPF was my inspiration)

In my final post on the topic, I ended up with a bastardized version of this using static's and attributes.

This way, it's fairly easy to use either syntax, depending on your personal preference.

The good thing about using inheritance is that you only have to define your delegate type once (in the base class), however I agree it is more verbose and cluttered.

Cheers!