The Problem
With the following class, I can query on IsOverdue using linq... but not using linq to sql (or linq to nhibernate). How frustrating!
class Loan
{
// Member details omitted
public bool IsOverdue
{
get
{
return DateReturned == null &&
(DateTime.Now - DateBorrowed).TotalDays > LoanPeriod;
}
}
}
This is a fundamental issue in relational to object mapping systems, but we can fix it using some linq trickery.
Disclaimer
I think my solution is very cool, but I'm not sure how acceptable it is for a production environment.
Please leave feedback if you like it / makes you want to vomit.
The change
Let's modify the Loan class implementation of IsOverdue:
#region IsOverdue Property
public bool IsOverdue
{
get { return IsOverdueProperty.GetValue(this); }
}
static public readonly QueryProperty<Loan, bool> IsOverdueProperty = QueryProperty.Register<Loan, bool>(
x => x.DateReturned == null && (DateTime.Now - x.DateBorrowed).TotalDays > x.LoanPeriod);
#endregion
Here we move the logic for IsOverdue into a separate QueryProperty member that takes care of the nasty details.
Hopefully you'll agree that the code is still fairly readable, and encapsulated with all the logic still bundled inside the Loan class.
By moving this logic into a lambda expression, we can now write queries for both database and object collections! e.g
// Database Access
using (var db = new RepositoryDatabase<Loan>())
var overdue = db.Where(y => y.IsOverdue && y.LoanPeriod == 5);
// Collection Access
using (var list = new RepositoryCollection<Loan>())
var overdue = list.Where(y => y.IsOverdue && y.LoanPeriod == 5);
More Goodness
When coding these query properties it can be very handy to reference other query properties. e.g.
#region DaysOverdue Property
public double DaysOverdue
{
get { return DaysOverdueProperty.GetValue(this); }
}
static public readonly QueryProperty<Loan, double> DaysOverdueProperty = QueryProperty.Register<Loan, double>(
x => x.IsOverdue ? (DateTime.Now - x.DateBorrowed).TotalDays - x.LoanPeriod : 0);
#endregion
#region OverdueFee Property
public decimal OverdueFee
{
get { return OverdueFeeProperty.GetValue(this); }
}
static public readonly QueryProperty<Loan, decimal> OverdueFeeProperty = QueryProperty.Register<Loan, decimal>(
x => (decimal)(x.DaysOverdue * 0.45));
#endregion
This allows us to create very complicated queries in a very simple manner.
The Catch
Too good to be true? You're right; there's a devil in the details.
You might have picked it up already - how does linq interpret my query property correctly?
By modifying the expression tree!
My Repository class implements the Where() and translates the expression; replacing any QueryProperty references with a call to the actual lambda expression.
The really ugly part is that it's a string based lookup ie. "<name>Property". Though I also check the type to be extra safe.
It's really not a bad trade off, considering it's very similar to how WPF DependencyProperty's work.
The Conclusion
This is a really easy and seamless way to implement custom queryable properties in your domain objects.
I've only touched on a fraction of the possibilities with this technique, but it shows the power and flexibility of linq.
Make sure you check out the sample application for the full code.
ed: Continue to Part II