6

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?

Ani
  • 111,048
  • 26
  • 262
  • 307
David
  • 15,750
  • 22
  • 90
  • 150

4 Answers4

4

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);
Ani
  • 111,048
  • 26
  • 262
  • 307
3

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)
Amy B
  • 108,202
  • 21
  • 135
  • 185
  • I think in the short term rearranging the Linq query as described was probably the best answer for me. However, I'm defniitely going to check out the LinqKit library that Ani recommended. – David Nov 14 '11 at 08:45
0

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

Polity
  • 14,734
  • 2
  • 40
  • 40
  • @DavidB - Your right, answer is unconstructive to the problem. I analysed it wrongly. Thanks for the comment – Polity Nov 08 '11 at 16:24
0

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));
samjudson
  • 56,243
  • 7
  • 59
  • 69
  • This doesn't work in lin2entity also it's too time consuming in normal situation. – Saeed Amiri Nov 08 '11 at 12:14
  • And was Linq2Entities in the original question? No, it wasn't. Not sure why it wouldn't work - why not? And what do you mean by 'time consuming'? – samjudson Nov 08 '11 at 12:21
  • I think GetDataSource means OP uses linq2entities (just guess), and by time consuming, compiling and invoking one expression all the time when we want to execute original query is time consuming, if you use it as normal expression all of them will be parsed to one expression and this will be execute one time. – Saeed Amiri Nov 08 '11 at 12:25
  • It's actually Linq 2 Sql but I didn't realise it mattered. Will the use of Compile() not mean that the specification won't get converted to SQL? – David Nov 08 '11 at 13:30
  • That is perhaps the case, yes. I'm afraid the alternative (trying to pass the entire expression of an expression through to the linq2sql query engine) is a bit beyond my knowledge I'm afraid. Perhaps the LinqKit magic post would be better. – samjudson Nov 08 '11 at 13:38
  • This will not work in LINQ2SQL (and also in EF), LINQKit must be used. – Mic Nov 10 '11 at 10:41