简体   繁体   中英

Linq Query Optimization in Entity Framework

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

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

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.

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