Linq: how to use specifications against associated objects
Asked Answered
C

4

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?

Claire answered 8/11, 2011 at 11:48 Comment(0)
S
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)
Slumlord answered 8/11, 2011 at 15:29 Comment(1)
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.Claire
A
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);
Alluvial answered 8/11, 2011 at 12:50 Comment(0)
S
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)
Slumlord answered 8/11, 2011 at 15:29 Comment(1)
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.Claire
V
0

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

Viglione answered 8/11, 2011 at 11:54 Comment(1)
@DavidB - Your right, answer is unconstructive to the problem. I analysed it wrongly. Thanks for the commentViglione
C
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));
Cupronickel answered 8/11, 2011 at 11:54 Comment(6)
This doesn't work in lin2entity also it's too time consuming in normal situation.Darya
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'?Cupronickel
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.Darya
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?Claire
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.Cupronickel
This will not work in LINQ2SQL (and also in EF), LINQKit must be used.Peggie

© 2022 - 2024 — McMap. All rights reserved.