2009-11-17 10 views
5

Sto riscontrando problemi nella creazione di una query LINQ di Entity Framework la cui clausola select contiene chiamate di metodo a oggetti non EF.Seleziona clausola contenente chiamate al metodo non EF

Il codice seguente fa parte di un'app utilizzata per trasformare i dati da un DBMS in uno schema diverso su un altro DBMS. Nel codice qui sotto, Ruolo è la mia classe personalizzata correlati ai DBMS, e le altre classi sono tutti generati da Entity Framework dal mio schema DB:

// set up ObjectContext's for Old and new DB schemas 
var New = new NewModel.NewEntities(); 
var Old = new OldModel.OldEntities(); 

// cache all Role names and IDs in the new-schema roles table into a dictionary 
var newRoles = New.roles.ToDictionary(row => row.rolename, row => row.roleid); 

// create a list or Role objects where Name is name in the old DB, while 
// ID is the ID corresponding to that name in the new DB 
var roles = from rl in Old.userrolelinks 
      join r in Old.roles on rl.RoleID equals r.RoleID 
      where rl.UserID == userId 
      select new Role { Name = r.RoleName, ID = newRoles[r.RoleName] }; 
var list = roles.ToList(); 

Ma chiamando ToList mi dà questo NotSupportedException:

LINQ alle entità non riconosce il metodo 'Int32 get_Item (System.String)' metodo e questo metodo non può essere tradotto in un'espressione negozio

Suoni come LINQ-to-Entities sono barfing sulla mia chiamata per estrarre il valore dal dizionario dato il nome come chiave. Ovviamente non capisco abbastanza di EF per sapere perché questo è un problema.

Sto utilizzando il provider di strutture di entità dotConnect for PostgreSQL di devart, anche se presumo a questo punto che questo non sia un problema specifico di DBMS.

so che posso farlo funzionare, dividendo il mio query in due query, in questo modo:

var roles = from rl in Old.userrolelinks 
      join r in Old.roles on rl.RoleID equals r.RoleID 
      where rl.UserID == userId 
      select r; 
var roles2 = from r in roles.AsEnumerable() 
      select new Role { Name = r.RoleName, ID = newRoles[r.RoleName] }; 
var list = roles2.ToList(); 

ma mi chiedevo se ci fosse un modo più elegante e/o più efficiente per risolvere questo problema , idealmente senza dividerlo in due domande.

In ogni caso, la mia domanda è due parti:

Prime, posso trasformare questa query LINQ in qualcosa che Entity Framework accetterà, idealmente senza suddivisione in due pezzi?

In secondo luogo, mi piacerebbe anche capire un po 'di EF, quindi posso capire perché EF non può stratificare il mio codice .NET personalizzato in cima all'accesso del DB. Il mio DBMS non ha idea di come chiamare un metodo su una classe Dictionary, ma perché EF non può semplicemente effettuare tali chiamate con il metodo Dictionary dopo che ha già estratto i dati dal DB? Certo, se volessi comporre più query EF insieme e mettere il codice .NET personalizzato nel mezzo, mi aspetterei che fallisca, ma in questo caso il codice .NET è solo alla fine, quindi perché è un problema per EF? Presumo che la risposta sia qualcosa del tipo "quella caratteristica non è stata convertita in EF 1.0" ma sto cercando un po 'di spiegazioni sul perché questo è abbastanza difficile da giustificare il fatto di lasciarlo fuori da EF 1.0.

risposta

10

Il problema è che quando si utilizza l'esecuzione ritardata di Linq, è davvero necessario decidere dove si desidera elaborare e quali dati si desidera attraversare il tubo all'applicazione client. Nel primo caso, Linq risolve l'espressione e tira tutti i dati ruolo di precursore di

New.roles.ToDictionary(row => row.rolename, row => row.roleid); 

A quel punto, i dati vengono spostati dal DB nel client e si trasforma in vostro dizionario. Fin qui tutto bene.

Il problema è che la vostra seconda espressione LINQ sta chiedendo Linq per fare la trasformazione del secondo DB utilizzando dizionario sul DB di farlo.In altre parole, sta cercando di capire come passare l'intera struttura del dizionario al DB in modo che possa selezionare il valore ID corretto come parte dell'esecuzione ritardata della query. Ho il sospetto che sarebbe risolvere bene se alterato la seconda metà di

var roles = from rl in Old.userrolelinks 
      join r in Old.roles on rl.RoleID equals r.RoleID 
      where rl.UserID == userId 
      select r.RoleName; 
var list = roles.ToDictionary(roleName => roleName, newRoles[roleName]); 

In questo modo, si risolve la vostra selezione sul DB (selezionando solo la rolename) come un precursore per l'elaborazione della chiamata ToDictionary (che dovrebbe fare sul client come ti aspetteresti). Questo è essenzialmente esattamente ciò che si sta facendo nel secondo esempio perché AsEnumerable sta trasferendo i dati al client prima di utilizzarlo nella chiamata ToList. Si potrebbe facilmente cambiarlo in qualcosa come

var roles = from rl in Old.userrolelinks 
      join r in Old.roles on rl.RoleID equals r.RoleID 
      where rl.UserID == userId 
      select r; 
var list = roles.AsEnumerable().Select(r => new Role { Name = r.RoleName, ID = newRoles[r.RoleName] }); 

e funzionerebbe lo stesso. La chiamata a AsEnumerable() risolve la query, estraendo i dati dal client per l'utilizzo in Select che lo segue.

Nota che non l'ho ancora testato, ma per quanto comprendo Entity Framework, questa è la mia migliore spiegazione per quello che sta succedendo sotto il cofano.

+0

Sembra che abbia sopravvalutato l'intelligenza di LINQ alle entità. Avevo pensato che L-to-E fosse abbastanza intelligente da scomporre un albero di espressioni nella parte che il DB può gestire, e un'altra parte che doveva risolvere usando chiamate .NET (fuori dal DB), e poi ricucire i due insieme. Se ti capisco bene, stai dicendo che L-to-E non è così intelligente, che semplicemente cerca di trasformare * tutto * nell'espressione in SQL, e se c'è qualcosa che non può trasformare (ad es. chiamata a un metodo oggetto .NET nella clausola select), quindi non riuscirà a eseguire? –

+1

Proprio così, Justin. Nota: sezionare una query tra i livelli automaticamente, si tratta di una 'query distribuita', che è un problema incredibilmente difficile da risolvere in generale. –

+0

Giusto. LINQ to Entities contiene tutto come un albero di espressioni che verrà utilizzato contro il database solo quando è * effettivamente * iterato. È "stupido" in questo modo, ma è perché, come dice Alex, le query distribuite sono un problema incredibilmente difficile da risolvere quando le cose diventano anche un po 'complesse. Sappiate che, tuttavia, è possibile utilizzare l'iterazione come segnalibro per estrarre i dati dal client. –

3

Jacob ha perfettamente ragione. Non è possibile trasformare la query desiderata senza dividerla in due parti, poiché Entity Framework non è in grado di tradurre la chiamata get_Item nella query SQL.
L'unico modo è scrivere la query LINQ su Entità e quindi scrivere una query LINQ su Oggetti al suo risultato, proprio come consigliato da Jacob.
Il problema è uno specifico di Entity Framework, non deriva dalla nostra implementazione del supporto di Entity Framework.

+0

+1. grazie per la rapida risposta-- bello vedere i venditori che saltano dentro per rispondere alle domande relative al loro codice! –

Problemi correlati