1

I have the following query, and what I'm looking for is to optimize the where clause. I don't like the multiple FirstOrDefault uses. Is there a way to implement it with Join maybe, or some other way so I will be able to access the FirstOrDefault only once?

var promos = await this._context
                       .SinglePromotions
                       .Include(p => p.Rewards)
                       .Include(p => p.InAppProduct)
                       .Include(p => p.PlayerSinglePromotions)
                       .ThenInclude(sp => sp.Player)
                       .ThenInclude(pl => pl.Purchases)
                       .Where(p => p.MinimumPlayerLevel <= Player.Level && 
                                   p.Placement == placement && 
                                   p.IsActive && 
                                   (p.PlayerSinglePromotions.All(sp => sp.PlayerID != Player.ID) || 
                                    (p.PlayerSinglePromotions.FirstOrDefault(sp => sp.PlayerID == Player.ID).PurchaseAmount < p.PurchaseLimit)) &&
                                    (p.PlayerSinglePromotions.All(sp => sp.PlayerID != Player.ID) || (p.PlayerSinglePromotions.FirstOrDefault(sp => sp.PlayerID == Player.ID).Player.Purchases.Count <= p.MaxInAppPurchasesByPlayer))
                       )
                       .ToListAsync();
2
  • write it how you would in SQL then written the linq which represents the SQL you wrote. LinqPad is a great tool to help you verify that the linq you are writing is turning into the correct sql. Commented Jun 6, 2019 at 14:40
  • Dose this transfer into sql, I am not 100% sure can you post the sql output and the actual query that gets genetated? Commented Jun 8, 2019 at 21:29

2 Answers 2

1

Well, probably there is a better solution, but you could replace your conditions where you use FirstOrDefault using Any extension method instead:

var promos = await this._context.SinglePromotions
.Include(p => p.Rewards)
.Include(p => p.InAppProduct)
.Include(p => p.PlayerSinglePromotions)
    .ThenInclude(sp => sp.Player)
    .ThenInclude(pl => pl.Purchases)
.Where(
    p => p.MinimumPlayerLevel <= Player.Level && 
    p.Placement == placement && 
    p.IsActive &&
   (p.PlayerSinglePromotions.All(sp => sp.PlayerID != Player.ID) ||
    p.PlayerSinglePromotions.Any(sp => sp.PlayerID == Player.ID 
                                   && (sp.Player.Purchases.Count <= p.MaxInAppPurchasesByPlayer || sp.PurchaseAmount < p.PurchaseLimit))))
.ToListAsync();
Sign up to request clarification or add additional context in comments.

Comments

1

You can prevent repetition of long expression in LINQ queries by using query syntax with the let clause. It's like declaring variables that contain the result of an expression. In your query two parts are repeated that can be captured in let variables:

var promos = await (
    from p in this._context.SinglePromotions
        .Include(p => p.Rewards)
        .Include(p => p.InAppProduct)
        .Include(p => p.PlayerSinglePromotions)
        .ThenInclude(sp => sp.Player)
        .ThenInclude(pl => pl.Purchases)
    let pspNotOfPlayer = p.PlayerSinglePromotions.All(sp => sp.PlayerID != Player.ID)
    let firstPromotion = p.PlayerSinglePromotions.FirstOrDefault(sp => sp.PlayerID == Player.ID)
    where
        p.MinimumPlayerLevel <= Player.Level 
        && p.Placement == placement 
        && p.IsActive
        && pspNotOfPlayer || firstPromotion.PurchaseAmount < p.PurchaseLimit
        && pspNotOfPlayer || firstPromotion.Player.Purchases.Count <= p.MaxInAppPurchasesByPlayer
    select p
    )
    .ToListAsync();

In many cases this will not only make the code more readable but also improve the generated SQL because repetitive subqueries can be replaced by CROSS APPLY clauses.

4 Comments

Sql optimization engine tends to handle this sort of stuff well so I am not sure how much better in regards with performance it will be, best way to see is to measure it. And is first or default supported in ef core?
@FilipCordas True, and yes, FirstOrDefault() is supported.
probably just me but I found EF core to frustrate me because you don't see what query is not supported until you look at the output console and see that it just pulled everything in memory and did the query locally.
@FilipCordas Not just you. So far to I think it's impossible to use EF core for enterprise applications because not all of the most common LINQ query operations are supported. Esp. grouping is a problem when it comes to server-side operation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.