Linq: cómo usar especificaciones contra objetos asociados

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?

preguntado el 08 de noviembre de 11 a las 11:11

4 Respuestas

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 soltero, 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);

respondido 08 nov., 11:16

Obviamente:

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)

respondido 08 nov., 11:19

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

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

respondido 08 nov., 11:15

@DavidB - Your right, answer is unconstructive to the problem. I analysed it wrongly. Thanks for the comment - Gobierno

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));

respondido 08 nov., 11:17

This doesn't work in lin2entity also it's too time consuming in normal situation. - Saeed Amiri

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

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

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

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

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.