Linq: how to use specifications against associated objects

829 views Asked by At

I'm using specifications in this kind of form:

public static Expression<Func<User, bool>> IsSuperhero
{
  get
  {
    return x => x.CanFly && x.CanShootLasersFromEyes;
  }
}

Now I can use this specification in the form:

var superHeroes = workspace.GetDataSource<User>().Where(UserSpecifications.IsSuperhero);

But I'm not sure how to use the specification against an associated object like this:

var loginsBySuperheroes = workspace.GetDataSource<Login>().Where(x => x.User [ ??? ]);

Is there a way to do this, or do I need to rethink my implementation of specifications?

4

There are 4 answers

1
Amy B On BEST ANSWER

Obviously:

var loginsBySuperheroes = workspace.GetDataSource<User>()
  .Where(UserSpecifications.IsSuperhero)
  .SelectMany(x => x.Logins);

This can be fun:

var secretBillionaires = workspace.GetDataSource<User>()
   .Where(UserSpecifications.IsSuperhero)
   .SelectMany(user => user.Logins)
   .Where(LoginSpecifications.IsSecretIdentity)
   .Select(login => login.DayJob)
   .Where(DayJobSpecifications.IsBillionaire)
1
Polity On

You can create your own custom QueryProvider as explained in detail here: http://msdn.microsoft.com/en-us/library/bb546158.aspx

6
samjudson On

I believe you need to compile and then invoke the expression:

var loginsBySuperheroes = GetLogins().Where(l => IsSuperhero.Compile().Invoke(l.User));

An alternative might be to pre-compile the expression:

var f = IsSuperhero.Compile();
var loginsBySuperheroes = GetLogins().Where(l => f(l.User));
0
Ani On

Essentially, you need to create an Expression<Func<Login, bool>> that collects the associated User from a Login and then applies the existing IsSuperhero predicate on that user. The canonical way to accomplish this is to use Expression.Invoke on the 'contained' expression (IsSuperHero in this case), replacing its parameters with appropriate arguments.

Unfortunately, this approach is quite messy to do by hand. Worse, many LINQ providers, such as LINQ to Entities, don't like this sort of 'expression inside an expression' approach at all. The way around this is to 'inline' the 'invoked' expression into the bigger expression so that it all looks like a single, giant, expression-tree.

Fortuantely, there's the handy library LINQKit that can help out with this:

#region LINQKit Magic

Expression<Func<Login, bool>> predicate = login => IsSuperHero.Invoke(login.User);
var expandedPredicate = predicate.Expand(); 

#endregion LINQKit Magic

var loginsBySuperheroes = workspace.GetDataSource<Login>().Where(expandedPredicate);