Reputation: 207
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();
Upvotes: 1
Views: 701
Reputation: 109109
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.
Upvotes: 1
Reputation: 39326
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();
Upvotes: 1