简体   繁体   中英

Linq to Entities, Inject Join Inside Query Expression

I'm trying to improve the performance for some of our LINQ queries and there is one little thing that has a lot of space for improvement: joins. Almost all my queries have some join that is used to filter the result, but are not selected into de result. And these filtering conditions are optional...

What I have today is something like:

var q = from t1 in context.Set<T1>() 
    where t1.mandatoryfilter >= 0
    select t1;

if (useFilter) 
{
    var q2 = from t1 in q
        from t2 in context.Set<T2>().Where(t2 => t2.fk == t1.pk).DefaultIfEmpty
        where t2.filterProperty == filterValue
        select t1;

    if (useFilter2) 
    {
        [...]

        return q3.ToList();
    }

    return q2.ToList();
} 
else 
{
    if (useFilter2) 
    {
        [...]

        return q2.ToList();
    }

    return q.ToList();
}

This will generate a projection of the query. And depending on the complexity/quantity of the filters it will generate more and more projections and the code need to be nested if I need to combine some of the filters. The resulting query can grow to a size where it gets too large to send over the internet (not actually too large XD but it becomes a performance issue) and the code itself gets hard to maintain.

I know I could just change back to string SQL... but that is not a very elegant solution is it?

I'd like to know if there is a way to inject the join and the filter directly do the Expression Tree, so the provider would not generate the projections and the code would be linear and simple.

ps.: very important information, I'm using EF6, Linq-to-Entities, default SqlClient.

Thank you all in advance.

I'm not too confident with the query-syntax version of LINQ, and don't have VS open, so I'm using the method-syntax to make sure I get it correct: but the same idea applies.

This is a fairly standard way of applying filters. You don't create new queries each time, you simply append a predicate for each filter. Works for joins too (and unless you're using a custom join you likely don't need to explicitly use .Join )

var people = context.Set<Person>()
                .Where(p => p.mandatoryFilter > 0);

if (filter.WithAddress != null)
    people = people.Where(p => p.Address == filter.WithAddress);

if (filter.WithBossName != null)
    people = people.Where(p => p.Boss.Name == filter.WithBossName);

return people.ToList();

If you use navigation properties you hardly ever need joins and you can apply most filter conditions as a single expression.

Let's suppose your T1 entity has a navigation property T2 . The filter expression will look like this:

q = q.Where(q.T2.filterProperty1 == filterProperty == filterValue);

You also see here that you can just append an expression to the existing query q .

Now it turns into a commonly applied pattern to add filter conditions to an IQueryable :

IQueryable<T1> query = from t1 in context.Set<T1>() 
                       where t1.mandatoryfilter >= 0
                       select t1;

if (filterValue2 != null)
    query = query.Where(t => t.T2.filterProperty == filterValue2);
if (filterValue3 != null)
    query = query.Where(t => t.T3.filterProperty == filterValue3);
if (filterValue4 != null)
    query = query.Where(t => t.T4.filterProperty == filterValue4);

(Note that query should be cast to IQueryable , otherwise the assignments query = query.... don't compile.)

And now you can even apply filters to 1:n associations

if (filterValue5 != null)
    query = query.Where(t => t.T5s.Any(t5 => t5.filterProperty == filterValue5));

You can't do this if you use join for filtering, because it would multiply the result set.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM