Alkampfer's Place

Il blog di Gian Maria Ricci
posts - 659, comments - 871, trackbacks - 80

My Links

News

Gian Maria Ricci Mvp Logo CCSVI in Multiple Sclerosis

English Blog

Tag Cloud

Article Categories

Archives

Post Categories

Image Galleries

I miei siti

Siti utili

Entity Framework e la corrispondenza con il modello ad oggetti.

Questo post nasce da un fatto strano che ho notato sviluppando un progetto con Entity Framework, considerate il seguente spezzone di codice.

1 using (NorthwindEntities context = new NorthwindEntities()) 2 { 3 IEnumerable<Customers> customers1 = 4 context.Customers 5 .Where(c => c.CustomerID.Contains("")); 6 Console.WriteLine(customers1.Count()); 7 8 IEnumerable<Customers> customers2 = 9 context.Customers 10 .ToList() 11 .Where(c => c.CustomerID.Contains("")); 12 Console.WriteLine(customers2.Count()); 13 }

Il codice è veramente banale, nel primo caso si effettua una query LINQ to entities chiedendo tutti i clienti il cui id contiene stringa nulla, nel secondo caso si effettua un'operazione molto simile, si caricano in memoria tutti i clienti (perchè nella linea 10 il ToList() essendo un not-deferred operator effettua la query nel database e carica gli oggetti in memoria) e poi si effettua un filtro sugli oggetti in memoria con la stessa condizione precedente, ovvero filtrare tutti i clienti il cui id contiene stringa nulla.

Il risultato di questo snippet è

0
91

ovvero filtrando sul database si ottengono zero oggetti mentre filtrando gli oggetti in memoria otteniamo 91 oggetti. Naturalmente il secondo risultato è quello corretto, dato che ogni stringa contiene logicamente la stringa nulla e quindi mi aspetto che vengano tornati tutti i clienti che non hanno un CustomerID nullo. Questo problema deriva da una pagina asp.net che deve soddifare queste spefiche. E' necessario mostrare tutti i clienti che contengono nel CustomerId una stringa digitata dall'utente con la condizione che se l'utente non digita nulla vengano tornati tutti i clienti.

Ora, senza entity framework basta fare una query sql con una condizione del tipo CustomerId like '%' + stringaricerca + '%' ed ottenere il comportamento previsto; EF invece traduce la query nel modo seguente

SELECT [Extent1].[Address] AS [Address], [Extent1].[Checked] AS [Checked], ... FROM [dbo].[Customers] AS [Extent1] WHERE (CAST(CHARINDEX(N'', [Extent1].[CustomerID]) AS int)) > 0

per il contains viene utilizzata la funzione CHARINDEX che purtroppo se si passa una stringa nulla fallisce sempre. Questo significa che con Entity Framework le query sul modello concettuale non mappano fedelmente le query fatte su oggetti in memoria. Dato che EF dovrebbe costituire un layer (repository) per il quale io "lavoro" solo su oggetti mentre EF si occupa di gestire le query da fare al database, mi attendo sempre che il comportamento di una query linq to entity produca gli stessi risultati della corrispondente linq to object fatta su oggetti in memoria. Sebbene questo possa sembrare un peccato veniale, dal punto di vista concettuale è fondamentale che questo requisito sia mantenuto.

Alk.

Print | posted on venerdì 20 febbraio 2009 19:22 | Filed Under [ Architettura ]

Feedback

Gravatar

# re: Entity Framework e la corrispondenza con il modello ad oggetti.

In realtà il problema è ancora più serio concettualmente parlando e non si limita affatto Ad Entity Framework, anzi.
Il problema è a monte, ovvero LINQ.
Faccio un esempio che vale sia per EF che ad esempio per LINQ To SQL: alcuni operatori LINQ non funzionano, semplicemente vanno in eccezione, ma nessuno ti vieta di utilizzarli nel codice e compilarli in tutta tranquillità.
Questo è il vero problema: qualsiasi LINQ to "qualchecosa", a seconda di che cosa sia questo "qualchecosa" avrà necessariamente delle limitazioni riguardo quello che si può o non si può fare, semplicemente perchè il provider non riuscirà a trasformare tutto l'expression tree della query in qualcosa digeribile dalla fonte dati finale.
Nel caso di EF e LINQ To SQL, Sql Server, in altre parole trasformare un expression tree in T-SQL. Addirittura in EF potrebbe capitare che il comportamento sia diverso anche a seconda del RDBMS, semplicemente perchè T-SQL è diverso da PL-SQL e query LINQ che funzionano in SqlServer non vanno su Oracle (occhio sto volutamente esagerando...).
In questo scenario capita non raramente che si sia costretti a mettere qua e là una .AsEnumerable per scatenare la query sulla fonte dati, mettere gli oggetti in memoria e da lì continuare la query, certi del fatto che LINQ To Objects supporta tutto.
Questo è uno sporco work-around, ma sporco sporco e non dipende da EF, sarebbe la stessa cosa con XLINQ.
Senza contare l'impossibilità di eseguire delle Where in cui l'espressione booleana non sia pensata apposta per la fonte dati finale... provate a lavorare anche solo semplicemete con proprietà calcolate in EF, non direttamente mappate sulla tabella, ma che siano il risultato di un calcolo algebrico su proprietà mappate, e provate a mettere in una Where una condizione su una di queste (intendo dire le proprietà calcolate): il motore di interpretazione dell'expression tree fallirà miseramente perchè non sarà in grado di trasformare in una clausola where T-SQL qualcosa che se dovessimo scrivere una query a mano riusciremmo tranquillamente a realizzare, semplicemente creando campi calcolati temporanei e impostando su questi una condizione booleana.
Il problema è sempre lo stesso ovvero com'è stato pensato LINQ: la necessità di realizzare provider di interpretazione di un expression tree, ma un expression tree può essere enormemente complesso visto che l'espressività di una query LINQ è altrettanto enorme, e ci vorrebbe uno sforzo gigantesco per realizzare motori di interpretazione degli expression tree buoni per tutte le stagioni, senza contare ovviamente la limitatezza intrinseca delle capacità di interrogazione delle fonti dati finali (se facessimo un LINQ To CSV, che magari già esiste, probabilmente supporterebbe un decimo di tutti gli operatori LINQ disponibili...).

Saluti
20/02/2009 19:56 | Roberto Messora
Gravatar

# re: Entity Framework e la corrispondenza con il modello ad oggetti.

Avendo giocherellato nella realizzazione di un provider LINQ to NHibernate conosco bene la difficoltà di tradurre un Expression Tree, solo che qualche provider di EF talvolta non implementa funzioni che sono abbastanza semplici. Nel caso del contains ad esempio basta usare un like, EF con il provider sql non supporta nemmeno il Single(), ma supporta il First() che a livello di traduzione è molto simile (il single fai un top 2 e lanci eccezione se trovi 2 righe lo avevo fatto cosi in LINQ2NH).
Se si va su proprietà calcolate allora non c'è dubbio che sia virtualmente impossibile tradurre una query correttamente, ma per un contains mi attenderei più aderenza al modello ad oggetti. ;)
Se metto una ocndizione su una proprietà calcolata e EF mi genera eccezione di funzionalità non supportata lo accetto, ma che mi traduca male la contains mi rende perplesso, una contains secondo me è perfettamente e semplicemente usabile in una fonte dati :P

alk.
20/02/2009 20:18 | gian maria
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET