Sto cercando di fare un 'outer join sinistro' in linq usando GroupJoin e SelectMany, ma poi voglio anche aggregare il risultato usando GroupBy e Sum.
Ma quando eseguo il codice qui sotto, ottengo:
System.NotSupportedException: 'L'entità o il tipo complesso' ... tableB 'non può essere costruito in una query LINQ to Entities.'
Repo<tableA>().All()
.Where(i =>
(i.Date >= dateF && i.Date <= dateT)
&&
i.EndOfMonth
)
.GroupJoin(
Repo<tableB>().All().Where(i => (i.fieldX = ...somevalue... )),
dt => dt.DayIndex, scd => scd.DayIndex, (dt, scd) =>
new {
dt = dt,
scd = scd,
}
)
.SelectMany(
jn => jn.scd.DefaultIfEmpty( new tableB { Count1 = 0, Count2 = 0 }), // runtime error here
(dt,scd) => new { dt=dt.dt, scd = scd}
)
.GroupBy(i => i.dt)
.Select(i => new CountListItem
{
Date = i.Key.Date,
CountField1 = i.Sum(o => o.scd.Count1),
CountField2 = i.Sum(p => p.scd.Count2)
})
.OrderBy(i => i.Date)
.ToList()
Quando faccio solo DefaultIfEmpty()
ottengo l'errore:
System.InvalidOperationException: 'Il cast sul tipo di valore' System.Int32 'non è riuscito perché il valore materializzato è nullo. Il parametro generico del tipo di risultato o la query devono utilizzare un tipo annullabile. '
Presumo, lo ammetto, questo perché Sum riscontra valori nulli.
Ho provato i.Sum(o => o.scd.Count1 ?? 0
, ma poi dice:
Operatore '??' non può essere applicato agli operandi di tipo 'int' e 'int' [sic]
Ho anche provato DefaultIfEmpty(new { Count1 = 0, Count2 =0})
ma questo mi dà>
... il tipo non può essere dedotto.
Come posso farlo funzionare?
Evitare la prima eccezione è facile: basta usare il modello standard per il join esterno sinistro con DefaultIfEmpty()
senza parametri.
Il secondo problema deriva dalla differenza tra i tipi di dati di query SQL e LINQ (C #). Le query SQL supportano i valori NULL
modo nativo e possono restituire NULL
anche se l'espressione di origine non è annullabile. Come hai notato con l'ultimo tentativo, LINQ (e in particolare il compilatore C #) non è soddisfatto di quella sintassi.
Il trucco è di promuovere il tipo non annullabile con il nullable usando l'operatore di cast di C # e quindi applicare l'operatore null-coalescing all'espressione risultante:
CountField1 = i.Sum(o => (int?)o.scd.Count1 ?? 0),
CountField2 = i.Sum(o => (int?)o.scd.Count2 ?? 0),