2014-10-29 17 views
6

Sto utilizzando una query Linq per ottenere i clienti e il loro indirizzo principale opzionale (un cliente può avere zero o più indirizzi). La gerarchia di oggetti è la seguente:query Linq NullReferenceException su più join a sinistra (a cascata)

  • clienti
    • CustomerAddress (contiene booleana proprietà principale)
      • Indirizzo

Questa è la domanda che sto usando :

Quando lo eseguo su un database (tramite EF) restituisce i valori correttamente. Quando lo eseguo in un contesto di simulazione con oggetti di memoria, ottengo un valore NullReferenceException : Riferimento oggetto non impostato su un'istanza di un oggetto.

sono stato in grado di correggere questo errore controllando per un valore nullo nel secondo LEFT JOIN perché la prima a sinistra join restituisce valori nulli:

join add in GetDBContext(c).address on new { addr_code = cusadd == null ? null : cusadd.Addr_Code } equals new { addr_code = add.Addr_Code } into grpadd 

Ho trovato un blogpost con la stessa conclusione, ma nessuna spiegazione: http://technologycraftsmen.net/blog/2010/04/14/multiple-outer-joins-in-linq-to-sql/

Perché questa query non funziona sugli oggetti locali e non su un database?

I join esterni a sinistra della cascata devono sempre essere scritti in questo modo in Linq?

Grazie per il vostro feedback!

risposta

6

Linq è meraviglioso, ma nessuna astrazione è perfetta. Questo è un caso in cui le astrazioni sottostanti stanno perdendo un po '.

Quando l'espressione viene eseguita con il contesto reale, viene convertita in un'istruzione Transact SQL utilizzando LEFT JOIN. L'espressione non viene mai effettivamente eseguita nel CLR, tutto avviene nel database. Anche se non ci sono record corrispondenti nella tabella giusta, la query ha esito positivo.

Quando si esegue la query sul proprio contesto fittato, l'effettiva esecuzione della query avviene nel CLR e non nel database. A quel punto, l'espressione funziona come se avessi scritto codice non-LINQ C#. Ciò significa che un test contro una proprietà di un oggetto che è null genererà la NullReferenceException che stai vedendo.

Potrebbe essere utile immaginare cosa accadrebbe se questo era solo un join tra due sequenze come:

var customers = new List<Customer> { new Customer { Id = 1, Name = "HasAddress" }, new Customer { Id = 2, Name = "HasNoAddress" } }; 

var addresses = new List<Address> { new Address { Id = 1, CustomerId = 1, Street = "123 Conselyea Street" } }; 


var customerAddresses = from Customer cus in customers 
         join address in addresses on cus.Id equals address.CustomerId into grouped 
         from ca in grouped.DefaultIfEmpty() 
         select new { CustomerId = cus.Id, Name = cus.Name, Street = ca.Street }; 

L'assegnazione "Street = ca.Street" lancerà una NullReferenceException, perché il nostro secondo cliente ha nessun indirizzo corrispondente. Come lo risolveremo?Con un operatore ternario, proprio come hai incluso nella correzione alla cascata unirsi:

var customerAddresses = from Customer cus in customers 
         join address in addresses on cus.Id equals address.CustomerId into grouped 
         from ca in grouped.DefaultIfEmpty() 
         select new { CustomerId = cus.Id, Name = cus.Name, Street = (ca != null) ? ca.Street : null }; 

Nel caso in cui si sta prendendo in giro il contesto, la vostra cascata join non è un join Transact SQL, è solo un normale C# utilizzo di oggetti e sequenze come nel mio esempio.

Non conosco alcun modo per scrivere il codice per essere ignaro delle differenze nelle regole tra l'esecuzione nel CLR e l'esecuzione in un database. Potrebbe essere possibile fare qualcosa di complicato fornendo un'istanza predefinita alla chiamata del metodo DefaultIfEmpty, ma a me sembra intrusivo.

Speriamo che questo sarà migliorato una volta che avremo l'operatore di propagazione nullo in C#.

+1

Grazie per la risposta! Conclusione: se la query di Linq verrà eseguita sia su un database che nel CLR, la query dovrebbe gestire i problemi con entrambi i casi. L'operatore di propagazione nulla potrebbe in effetti fornire un codice più pulito. –

+2

Ottima risposta, mi sono imbattuto in questo io stesso con test dato una rappresentazione in memoria. La difficoltà consiste nell'assicurarsi che non si utilizzi un metodo o un operatore che non è traducibile nel database, altrimenti finirà per lanciare o tornare più dal database, quindi dovrebbe quindi eseguire il codice CLR sul filtro futuro. –

Problemi correlati