La mia query Linq continua a restituire l'errore null su FirstOrDefault
The cast to value type 'System.Int32' failed because the materialized value is null
perché non trova alcun record da abbinare su ClinicalAssetID dalla tabella ClinicalReading, abbastanza giusto!
Ma voglio che i campi nella mia pagina dei dettagli appaiano vuoti se la tabella non ha una voce corrispondente.
Ma come posso gestire il problema null quando uso l'ordine per funzione?
Codice attuale:
var ClinicalASSPATINCVM = (from s in db.ClinicalAssets
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subASSPAT in AP.DefaultIfEmpty()
join ci in db.ClinicalINSs on s.ClinicalAssetID equals ci.ClinicalAssetID into AI
from subASSINC in AI.DefaultIfEmpty()
join co in db.ClinicalReadings on s.ClinicalAssetID equals co.ClinicalAssetID into AR
let subASSRED = AR.OrderByDescending(subASSRED => subASSRED.MeterReadingDone).FirstOrDefault()
select new ClinicalASSPATINCVM
{
ClinicalAssetID = s.ClinicalAssetID,
AssetTypeName = s.AssetTypeName,
ProductName = s.ProductName,
ModelName = s.ModelName,
SupplierName = s.SupplierName,
ManufacturerName = s.ManufacturerName,
SerialNo = s.SerialNo,
PurchaseDate = s.PurchaseDate,
PoNo = s.PoNo,
Costing = s.Costing,
TeamName = s.TeamName,
StaffName = s.StaffName,
WarrantyEndDate = subASSPAT.WarrantyEndDate,
InspectionDate = subASSPAT.InspectionDate,
InspectionOutcomeResult = subASSPAT.InspectionOutcomeResult,
InspectionDocumnets = subASSPAT.InspectionDocumnets,
LastTypeofInspection = subASSINC.LastTypeofInspection,
NextInspectionDate = subASSINC.NextInspectionDate,
NextInspectionType = subASSINC.NextInspectionType,
MeterReadingDone = subASSRED.MeterReadingDone,
MeterReadingDue = subASSRED.MeterReadingDue,
MeterReading = subASSRED.MeterReading,
MeterUnitsUsed = subASSRED.MeterUnitsUsed,
FilterReplaced = subASSRED.FilterReplaced
}).FirstOrDefault(x => x.ClinicalAssetID == id);
Ho provato questo ma non funziona
.DefaultIfEmpty(new ClinicalASSPATINCVM())
.FirstOrDefault()
L'errore è stato:
CS1929 'IOrderedEnumerable<ClinicalReading>' does not contain a definition for 'DefaultIfEmpty' and the best extension method overload 'Queryable.DefaultIfEmpty<ClinicalASSPATINCVM>(IQueryable<ClinicalASSPATINCVM>, ClinicalASSPATINCVM)' requires a receiver of type 'IQueryable<ClinicalASSPATINCVM>'
Sentiti un po 'più vicino con questo, ma ancora errori
let subASSRED = AR.OrderByDescending(subASSRED => (subASSRED.MeterReadingDone != null) ? subASSRED.MeterReadingDone : String.Empty).FirstOrDefault()
Errore:
CS0173 Type of conditional expression cannot be determined because there is no implicit conversion between 'System.DateTime?' and 'string'
L'errore originale indica che alcune delle seguenti proprietà della classe ClinicalASSPATINCVM
- MeterReadingDone
, MeterReadingDue
, MeterReading
, MeterUnitsUsed
o FilterReplaced
sono di tipo int
.
Ricorda che qui è subASSRED
let subASSRED = AR.OrderByDescending(subASSRED => subASSRED.MeterReadingDone).FirstOrDefault()
potrebbe essere null
(nessun record corrispondente).
Ora guarda questa parte della proiezione:
MeterReadingDone = subASSRED.MeterReadingDone,
MeterReadingDue = subASSRED.MeterReadingDue,
MeterReading = subASSRED.MeterReading,
MeterUnitsUsed = subASSRED.MeterUnitsUsed,
FilterReplaced = subASSRED.FilterReplaced
Se fosse LINQ to Objects, tutti questi genererebbero NRE (Null Reference Exception) in fase di esecuzione. In LINQ to Entities questo viene convertito ed eseguito come SQL. SQL non ha problemi con espressioni come subASSRED.SomeProperty
perché SQL supporta NULL
naturalmente anche se SomeProperty
normalmente non consente NULL
. Quindi la query SQL viene eseguita normalmente, ma ora EF deve materializzare il risultato in oggetti e la proprietà dell'oggetto C # non è nulla , quindi l'errore in questione.
Per risolverlo, trova le proprietà int
e usa il seguente modello all'interno della query:
SomeIntProperty = (int?)subASSRED.SomeIntProperty ?? 0 // or other meaningful default
o cambiare il tipo di proprietà dell'oggetto ricevente in int?
e lasciare la query originale così com'è.
Fai lo stesso per qualsiasi proprietà di tipo non annullabile, ad esempio DateTime
, double
, decimal
, Guid
ecc.
Il tuo problema è perché DefaultIfEmpty viene eseguito come AsQueryable. Esegui AsEnumerable
e funzionerà:
// create the default element only once!
static readonly ClinicalAssPatInVcm defaultElement = new ClinicalAssPatInVcm ();
var result = <my big linq query>
.Where(x => x.ClinicalAssetID == id)
.AsEnumerable()
.DefaultIfEmpty(defaultElement)
.FirstOrDefault();
Questo non porterà a una penalità per le prestazioni!
I sistemi di gestione del database sono estremamente ottimizzati per la selezione dei dati. Una delle parti più lente di una query del database è il trasporto dei dati selezionati al processo locale. Quindi è saggio lasciare che il DBMS esegua la maggior parte della selezione e solo dopo aver appreso che si hanno solo i dati che si prevede di utilizzare, spostarli nel processo locale.
Nel tuo caso, hai bisogno al massimo di un elemento dal tuo DBMS e, se non c'è nulla, vuoi usare invece un oggetto predefinito.
AsQueryable
sposterà i dati selezionati nel processo locale in modo intelligente, probabilmente per "pagina" dei dati selezionati.
La dimensione della pagina è un buon compromesso: non troppo piccola, quindi non devi chiedere la pagina successiva troppo spesso; non troppo grande, in modo da non trasferire molti più elementi di quelli effettivamente utilizzati.
Inoltre, a causa dell'istruzione Where
ci si aspetta comunque al massimo un elemento. In modo che venga recuperata una "pagina" completa non è un problema, la pagina conterrà solo un elemento.
Dopo aver recuperato la pagina, DefaultIfEmpty
controlla se la pagina è vuota e, in tal caso, restituisce una sequenza contenente defaultElement
. In caso contrario, restituisce la pagina completa.
Dopo DefaultIfEmpty prendi solo il primo elemento, che è quello che vuoi.