rtev
rtev

Reputation: 1122

Mixing Func and Expression Predicates in Linq To Entity Queries

Given an an entity called Fruit:

public class Fruit
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Family { get; set; }
    public bool Edible { get; set; }
}

The following Linq-To-Entities query:

var familiesWithAllEdibleFruits = context
    .Fruits
    .GroupBy(fruit => fruit.Family)
    .Where(group => group.All(fruit => fruit.Edible));

generates a single SQL statement that selects the correct records:

SELECT 
    [Project4].[C1] AS [C1], 
    [Project4].[Family] AS [Family], 
    [Project4].[C2] AS [C2], 
    [Project4].[Id] AS [Id], 
    [Project4].[Name] AS [Name], 
    [Project4].[Family1] AS [Family1], 
    [Project4].[Edible] AS [Edible]
    FROM ( SELECT 
        [Project2].[Family] AS [Family], 
        [Project2].[C1] AS [C1], 
        [Project2].[Id] AS [Id], 
        [Project2].[Name] AS [Name], 
        [Project2].[Family1] AS [Family1], 
        [Project2].[Edible] AS [Edible], 
        CASE WHEN ([Project2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
        FROM ( SELECT 
            [Distinct1].[Family] AS [Family], 
            1 AS [C1], 
            [Extent2].[Id] AS [Id], 
            [Extent2].[Name] AS [Name], 
            [Extent2].[Family] AS [Family1], 
            [Extent2].[Edible] AS [Edible]
            FROM   (SELECT DISTINCT 
                [Extent1].[Family] AS [Family]
                FROM [dbo].[Fruits] AS [Extent1] ) AS [Distinct1]
            LEFT OUTER JOIN [dbo].[Fruits] AS [Extent2] ON ([Distinct1].[Family] = [Extent2].[Family]) OR (([Distinct1].[Family] IS NULL) AND ([Extent2].[Family] IS NULL))
        )  AS [Project2]
        WHERE  NOT EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[Fruits] AS [Extent3]
            WHERE (([Project2].[Family] = [Extent3].[Family]) OR (([Project2].[Family] IS NULL) AND ([Extent3].[Family] IS NULL))) AND ([Extent3].[Edible] <> cast(1 as bit))
        )
    )  AS [Project4]
    ORDER BY [Project4].[Family] ASC, [Project4].[C2] ASC

But the following code where the inner predicate is an Expression:

Expression<Func<Fruit, bool>> innerPredicate = fruit => fruit.Edible;

var familiesWithAllEdibleFruits = context
    .Fruits
    .GroupBy(fruit => fruit.Family)
    .Where(group => group.All(innerPredicate));

gets stuck in the compiler's craw:

'System.Linq.IGrouping< string, Fruit >' does not contain a definition for 'All' and the best extension method overload 'System.Linq.Enumerable.All< TSource >(System.Collections.Generic.IEnumerable< TSource >, System.Func< TSource, bool> )' has some invalid arguments

Yet when the outer predicate is encapsulated in an expression:

Expression<Func<IGrouping<string, Fruit>, bool>> outerPredicate =
    group => group.All(fruit => fruit.Edible);

var familiesWithAllEdibleFruits = context
    .Fruits
    .GroupBy(fruit => fruit.Family)
    .Where(outerPredicate);

things work correctly.

I'd like to understand the behavior I'm seeing here better. It looks like the call to 'All' inside the outer predicate will not allow Expression parameters. Is it possible to easily compose queries interchangeably with Funcs and Expressions (as in the 2nd example) or is this an inherent limitation?

Upvotes: 1

Views: 458

Answers (1)

Servy
Servy

Reputation: 203812

The best that you're going to be able to manage is to use LINQKit here:

Expression<Func<Fruit, bool>> innerPredicate = fruit => fruit.Edible;

var familiesWithAllEdibleFruits = context
    .Fruits
    .GroupBy(fruit => fruit.Family)
    .Where(group => group.All(fruit => innerPredicate.Invoke(fruit)))
    .Expand();

As for why, what your first code snippet has is an expression that represents an expression that represents a Func, while what you want to have is an expression that just represents a Func. You need a way of "unwrapping" the expression. In short, that's simply not easy. It takes quite a bit more work to compose expressions than it does to compose regular delegates, as you need to go around unwrapping the body of each expression and inlining it into the outer expression.

Upvotes: 1

Related Questions