Aspecte interesante ale LINQ de lucru la sql

Pe postul meu anterior a avut loc luna trecută, cred că e timpul pentru a continua. De data aceasta, hai sa vorbim despre Moștenirea Mapping'e, ei bine, un interes special în finalul articolului va fi o surpriză.

Probleme cu discriminatorului

Desigur, vom păstra în natura noastră bază de date polimorfă a datelor. De exemplu, aceasta este esența CustomerOperation, care reflectă o operație care poate fi realizată pe consumator. Operațiunile sunt efectuate în principal prin intermediul serviciilor, astfel încât nu CustomerServiceOperation moștenitor, și așa cum avem un WebTracking'a mecanism pentru care există WebTrackingOperation. Dar destule cuvinte, este mai bine pentru a arăta codul:

Să încercăm să obțineți niște WebTracking-operație. Și aici este codul:

Totul este bine. Dar ce s-ar întâmpla dacă am uitat dintr-o dată să se înregistreze tip WebTrackingOperation în moștenirea Maparea atributelor? Dacă ne uităm să facem acest lucru, o astfel de cerere va fi difuzat

LINQ inteligent pentru SQL! Dar o excepție ar fi cea mai bună alegere, în opinia mea umila. Ei bine, ce dacă am uitat să se înregistreze tipul, dar umblat pe masa acestor operațiuni și un anumit tip schimbat în mod direct în baza de script-ul (de exemplu, înainte de toate operațiile au fost customerservice, iar acum acolo WebTracking, iar unele dintre nevoia vechi de a fi actualizate). Să încercăm să scădeți operarea cu strainii discriminator din baza de date:

Aspecte interesante ale LINQ de lucru la sql

Expectedly dedus tip de bază esență. Ce se întâmplă dacă încercați să salvați un astfel de miracol?

Nimic teribil sa întâmplat. Numele sa schimbat, restul rămâne aceeași:

Aspecte interesante ale LINQ de lucru la sql

Ok, hai să încercăm să-l scrie în baza de WebTrackingOperation (cu Refactorizare pe care l-am descris mai sus, codul pentru a crea astfel de entități au apărut în mod necesar). Aceasta incercare va eșua cu NullReference amuzant, dovada:

Aspecte interesante ale LINQ de lucru la sql

Deși nu să acorde o atenție, că eroarea se încadrează într-un Mindbox.Data.Linq.dll misterios, se încadrează în același mod ca și în LINQ clasic la SQL. Erorilor, după cum se poate vedea, în general, nu am mai văzut, în cazul în care să caute problema, astfel încât bug-ul nu este foarte plăcut. Fii atent și nu uitați să indice toate tipurile de entități polimorfă atribute Moștenirea Mapping'a.

Și, în sfârșit, despre un discriminator proprietate amuzant. Să încercăm să creeze o nouă clasă de bază CustomerOperation entitate, atribuie un discriminator-vă și de a salva:

Acest lucru se va insera valoarea câmpului generat Discriminatorul = WebTracking, dar după LINQ SQL insera perevystavit discriminatorului în sine - adică, fă-o să setter cu un șir gol (deoarece este valoarea implicită pentru tipul de bază a fost specificat în atributul Mapping moștenire):

Aspecte interesante ale LINQ de lucru la sql

Dacă acest comportament nu va convine, atunci există o soluție simplă: în discriminator setter'e pentru a ignora expunerea unui șir gol.

enumerare nechemat

În LINQ SQL, există un (nu, bine, desigur niciodată singur, dar acum este vorba despre un anumit) moment foarte neplăcut. Aproape întotdeauna, în cazul în care o cerere de LINQ a fost construită în așa fel încât nu este posibil smappit pe SQL, LINQ la apelarea recenzorul aruncă o excepție. Textul acestor excepții este cunoscut tuturor, ea poate fi ceva de genul (a luat o pereche de bugtracker): „Accesul membrilor System.DateTime DateTimeUtc«a»Itc.DirectCrm.Model.CustomerAction«nu este legală de tipul»System.Linq.IQueryable` 1 [Itc.DirectCrm.Model.CustomerAction] "sau" Metoda „Boolean Evaluează [CustomerLotteryTicket, Boolean] (System.Linq.Expressions.Expression`1 [System.Func`2 [Itc.DirectCrm.Promo.CustomerLotteryTicket, System.Boolean ]], Itc.DirectCrm.Promo.CustomerLotteryTicket) „nu are nici o traducere acceptată la SQL“. Cuvântul cheie este aproape. Uneori, LINQ SQL poate constata că în loc să arunce o astfel de excepție, este mai bine să deducă mai multe entități în memorie și de memorie trebuie să facă unele modificări. Este foarte trist din mai multe motive: nu este documentată în nici un fel (la cunoștințele mele), din aceasta cauza, cad uneori un OutOfMemory (deoarece spicuite esența nu va părăsi contextul, deși codul va arata ca si cum ai dedus obiecte anonime care se vor aduna rapid GC), precum și din cauza bug-uri cu moștenirea de cartografiere. De fapt, să ne uităm la un bug.

șablon de acțiune cu id = 20 este EmailMailingActionTemplate - Email-mail (am verificat), să-l scade:

Aspecte interesante ale LINQ de lucru la sql

Bine. Acum, încercați să solicite această acțiune pentru a executa cererea șablonul zabagovanny:

Id-ul am restricționat în mod deliberat, pentru a arăta că aceasta este interogarea problemă. EffectiveEndDateTimeUtc nu este o coloană, este doar o proprietate a clasei, în fața Select nu a fost numit AsEnumerable provocare, așa că în LINQ teorie ar trebui să arunce una foarte excepțiile, exemple din care am citat mai sus. Dar nu. Cererea este tradus în următoarea sql (pur și simplu Entitate solicitare):

După o astfel de solicitare anterioară (trăgând un anumit șablon Id) returnează modelul de acțiune al tipului de bază:

enumerare automată, așa cum se arată prin acest rezultat, pur și simplu nu acorde atenție la moștenirea Mapping, creând întotdeauna esența clasei de bază. enumerare automată frică!

Mi-e teamă că nu reflectă exact ceea ce se întâmplă de fapt. LINQ nu tratează următorul cod ca AsEnumerable. Se efectuează de cartografiere, astfel încât mapitsya, iar restul se face în memorie. De exemplu, dacă nu, trebuie doar să selectați proprietățile entității și proprietăți Selectați dintr-o entitate afiliată va fi creată în memoria entităților legate numai.
Un sfat bun ar fi să nu aibă proprietățile entității care nu sunt mapyatsya pe SQL - ceea ce le face metodele pe care a fost imediat problemă vizibilă.

FixedItems

Este posibil să fi observat că partea în care am vorbit despre problemele cu discriminatorului, am fost introdus următorul cod:

După cum puteți ghici, toate căile de acces la baza de date a codului prin intermediul magaziei. Fiecare depozit are metode ajutatoare pentru entitate pentru anumite caracteristici (consumatori metoda GetByName depozit, etc.), dar, din moment ce nu este practic pe toată lumea pentru a crea o metodă proprie, atunci fiecare din depozitul nostru este o proprietate de articole - este entitățile relevante doar IQueryable. Dar ce este FixedItems pe CustomerOperationRepository? Lucru amuzant este - FixedItems proprietate din codul arata ca acest lucru:

Aceasta este - una dintre cârje pe care trebuie să le folosească atunci când se lucrează cu LINQ la SQL. Pentru a explica problema, este necesar pentru a descrie situația un pic.

CustomerOperationRepository oferă acces la entități CustomerOperation, dar însăși esența CustomerOperation este un element al campaniei (unitatea noastră de mare, la care este legat de o mulțime de alte entități). Aceste entități, cum ar fi de validare, există multe proprietăți comune, astfel încât acestea sunt moștenite de la un depozit de clasă și, de asemenea, sunt moștenite. Ca toate elementele campaniei sunt moștenite de la interfața ICampaignItem și clasă de bază depozit primește tip de entitate ca primul generic'a parametru:

Problema este că metodele acestei clase de tratament la câmpurile TCampaignItem terminat de utilizat interfață ICampaignItem, și are o problemă cunoscută. care LINQ SQL nu creează proprietăți mappa definite în interfețele. Prin urmare solicita, de exemplu, toate operațiunile asociate cu campania (În cazul în care (articol => item.CampaignId == campaign.Id)), scade cu InvalidOperationException: MappingOfInterfacesMemberIsNotSupported: ICampaignItem, CAMPAIGNID. În același timp, adăugarea la lanțul de IQueryable aparent inutile Selectați (item => Element) rezolve în mod miraculos toate problemele. Aceeași metodă poate fi utilizată în locul operatorului object.Equals ==: În cazul în care (item => Egali (item.CampaignId, campaign.Id)), o astfel de cerere este transmisă fără utilizarea FixedItems.

Nul / Nu este nul în diferite moștenitori

bug-ul scurt și simplu. LINQ SQL nu suportă unul și același câmp setări diferite nule și nu nul în moștenitori. De exemplu:

PerformActionCustomerServiceOperation necesită valoare operationStepGroupId a fost întotdeauna (acest lucru necesită un model de domeniu), dar același domeniu în operationStepGroupId IdentificationTrackingOperation poate să lipsească. Clasa de bază pentru cele două entități - CustomerOperation, în care nu operationStepGroupId nu, se adaugă separat în acest domeniu PerformActionCustomerServiceOperation și IdentificationTrackingOperation.

Iată cum se întâmplă. Notați valorile CanBeNull și tipul de vorbitori în diferite entități:

Ei bine, bine, dar acum vom încerca să citească din baza de date pe lista operațiunilor. Una dintre aceste operațiuni - IdentificationTrackingOperation, care îi lipsește OperationStepGroupId.

Încercarea eșuează cum era de așteptat, pentru că obținem InvalidOperationException: CannotAssignNull: System.Int32.

Cum de a face cu ea? Am făcut simplu - LINQ permis să PerformActionCustomerServiceOperation nici o valoare pentru OperationStepGroupId, iar validarea acestuia este o verificare separată.

Cei care au citit, surpriza

Este posibil să fi observat că erorile din LINQ în capturi de ecran mele sunt în scădere în ansamblul numit Mindbox.Data.Linq.dll. Da, am forknuli LINQ to SQL. De exemplu, acum pentru înregistrarea entităților moștenitorilor pot fi utilizate nu numai InheritanceMappingAttribute, dar AddInheritance metoda void(Cod obiect) pe Mindbox.Data.Linq.Mapping.MindboxMappingConfiguration, care să permită moștenitorilor să se înregistreze în entități diferite ansambluri.

furculița nostru poate fi pus prin Nuget: Mindbox.Data.Linq.

Poate vrei ceva pentru a ajuta sau să profite de - noroc cu asta.