29. oktober 2010 - 13:59Der er
27 kommentarer og 1 løsning
Hvilken arkitekltur skal jeg vælge at bruge med Entity Framework
Jeg har altid kodet i 3 lag (data, forretning og præsentation), der har fungeret fint. Men jeg er i tvil om jeg bør overveje en anden arkitektur i min kode, specielt når jeg gerne vil benytte mig af Entity Framework.
Jeg har altid prøvet at holde lagene godt adskilt og ikke lade præsentationslaget gå uden om forretningslaget osv. Men med EF (eller måske bare generelt med OR Mapper) synes jeg at kunne se at man implementere tingene anderledes...
Skriver du så dine egne POCO klasser og vil disse ligge i forretnings laget? Hvordan og hvorledes skal man forholde sig til at oprette og rette i databasen, skal man går der direkte på sin "Manager"? Som jeg umildbart ser det kommer datalaget meget tæt på UI, eller har jeg misforstået noget?
Jeg bruger EF designeren til at danne min orm, og den klasse ligger internt i mit datalag, da det er her den skal bruges.
Hvis datastrukturen ændres er det også kun EF der skal opdateres, og da kan jeg hurtigt se om alt er ok, da datatilgangen til de enkelte felter jo netop er typestærk på grund af EF, det er blandt andet derfor jeg bruger EF (eller en anden orm) i mit datalag istedet for at tilgå felter med strenge o.s.v.
Men dette er selvfølgelig min holdning, jeg ville da gerne høre om der er andre der har en anden mening. :-)
Ja jeg har en anden holdning, men det havde du nok regnet ud bkp :)
Jeg har selv tidligere lavet den "gamle" model der og synes ikke rigtigt den modsvarer det man kan idag med IQueryable. Imho så har datalaget fået en lidt anden betydning end i gamle dage hvor man kaldte sprocs med dynamisk sql inside det meste af tiden.
Problemet med IList/ IEnumerable modellen som reelt modsvarer den gamle 3-lags DAL-BLL-PLL er at man ikke har mulighed for dynamisk at udvælge felter, sortering mv. Man vil altid havne i situationen med mange metoder, en metode til at pille Id'er ud, en anden til en enkelt entitet, en tredie der måske kan sortere udfra et parameter input osv. Altså en masse kodegentagelse fra det ene lag til det næste.
Jeg foretrækker IQueryable hvor man uden problemer kan smide hvilke felter man ønsker retur inkl. where-clause, sortering mv.
Nu ved jeg godt.. og det er jo derfor du nævner POCO's, disse kan nu godt smides ind i EF, men jeg foretrækker nu bare at bruge de entiteter der dannes i designeren, at nogle vil mene det forurener og strider imod det ene og andet princip. Ja jo.. måske, men tiderne ændrer sig og måske er den traditionelle 3-lags forældet. Lang diskussion :) men i bund og grund så har jeg ændret min mening ganske radikalt om hvad man bør og ikke bør, for mig handler det om at udnytte tingene optimalt og bruge sine evner på komplicerede algoritmer, threading mv. istedet for at kæmpe imod udviklingen og presse en firkant igennem en trekant, just because you can :)
Anyway... tag et kig på Repository Pattern, det er sådan en fin mellemvej som tilfredsstiller langt de fleste projekter. Og frygt ej.. den overholder stadig 3-lags, bare med en moderne tilgang og brug af IQueryable.
Tingene forholder sig naturligvis anderledes når du skal bygge distribuerede enterprise applikationer, men hvor ofte sker det lige?
3 lags princippet er rigtig nok en ældre sag, og jeg forstår udemærket din tankegang, og jeg vil heller ikke sige at jeg konsekvent bruger 3 lags princippet i alt hvad jeg laver (så kom jeg ud af skabet). Men hvis du har et projekt med en hel del udviklere som måske ikke alle kender til datamodellen, og hvordan man bedst behandler sine data, så er det en fordel at dba eller en anden styrer datalaget og derved sikrer sig at data hentes og skrives mest hensigtsmæssigt. EF har selvfølgelig den fordel at den i mange tilfælde finde den bedste og hurtigste vej til dine data, men derfor mener jeg alligevel at dem der er tæt på designfasen af databasen også skal styre datalaget. Jeg vil helst ikke komme med eksempler (kunne komme til at støde gamle kollegaer), men jeg har siddet i en stor gruppe hvor ikke alle forstod hvordan en MS Sql arbejdede, og det gav store performance problemer, derfor indførte vi et ordentligt datalag som dengang byggede på Linq to Sql, og det gjorde en god forskel.
Glemte lige at jeg er helt enig i at man bør kigge nærmere på Repository Pattern, men samtidig bør dem der har indsigt i data design have ansvaret for data, og ikke den enkelte udvikler. Det har hidtil været nemmest at styre med et datalag, men jeg vil heller ikke stoppe udviklingen, og er ikke bange for at afprøve nye metoder ;-)
Ja mange udviklere kender til den traditionelle tilgang, men jeg tager altid en snak med dem som måske ikke lige er med på L2S/ EF -beatet, de fanger hurtigt konceptet. Vi plejer at lave et design pattern hvor Repository Pattern er det centrale og så er alle udviklere med på tanken.
Og ja... jeg er 100% enig når du nævner hvordan SQL arbejder, men jeg har gjort et stort slag for at alle udvikler med en sql-profiler når der skrives strukturel Linq. Det kræver til gengæld en vis disciplin at sidde på den måde, men så igen... Jeg er også ham der skriver tests osv. for mig er det intet problem :) , jeg tror det handler lidt om at give ud af sin erfaring til de udviklere som ikke lige har de fornødne kompetencer. Udviklere har nu en stor fordel... "Vi lærer hurtigt :)"
Jeg fik ikke nævnt tidligere, men sådan noget som insert, update, delete, ja dem holder jeg stadigvæk 100% i datalaget/ repositoriet om man vil. Vi kan ikke have sådanne ting flyvende rundt :)
Jeg tror vi er enige, det virker ihvertfald sådan - du må ikke opfatte det som sort eller hvidt det jeg skriver, i min verden handler det bare om at finde den bedste og mest optimale løsning. No more, no less :)
Hej igen og tusind tak for alle jeres kommentare!!!!
Jeg har nu set video og hentet diverse projekter for at få en fornemmelse af hvordan jeg bruger Repository mønstret :-) Men jeg er lidt forvirret :-/ I flere af de eksempler jeg har hentet bruges der også et mønster der hedder "Unit Of Work".
Hvis jeg har forstået det rigtigt så skal det implementeres som følger:
Man har et Domain lag som indeholder ens POCO klasser, samt et repository med interfaces, som angiver funktionerne for at hente og vedligeholde data i DB. Bør der være et interface for hver POCO klasse?
Så har man et datalag, som indeholder EF modellen, samt implementeringer af de interfaces som er angivet i Repository.
Når man så vil tilgå data fra ens præsentationslag, gør man det ved hjælp af de interfaces, der er deifneret i Repository. Hvis det er sådan det skal virke giver det meget god mening. men i nogle af de eksempler jeg har hentet, tilgå de alligevel datalaget i Præsentationen (Context klassen der er genereret af EF):-/
Mht. implementeringen, så jo, ideelt set bør der være et interface for hver POCO, imho er det lidt overflødigt, en smagssag og krav til arkitekturen. Den ideelle model... Præsentationslaget skal kalde til servicelaget (som du kalder domainlag), præsentationslaget kender intet til datalaget, men bruger kun de services der er til rådighed. Servicelaget kender derimod fint til Repository (datalaget om man vil lidt simplificeret), men har ikke kendskab til hvordan data persisteres eller hentes.
Nu synes jeg så at man skal udnytte IQueryable, det kan man gøre på flg. måde: lidt pseudo:
Servicelag: class ForecastService { static IList<POCOForecast> GetLatestForecast() { var repository = new WeatherRepository();
var result = from f in repository.Forecast where f.Date > now - 1day orderby f.Date select new POCOForecast() { Id = f.Id, DayName = f.DayName, Temp = f.Temperature } return result.ToList(); } }
Repository: class Weather { public IQueryable<Forecast> Weather() { return context.Forecast; } } public void Delete(Forecast ent) {
} public void Update(Forecast ent).... __________________________________________
Nu skal du ikke lige tage dig af indholdet, det er mere for at vise dig et pattern hvor du udnytter IQueryable fra servicelaget. Håber du kan se modellen :) Ellers bare skyd løs :)
Nu nævnte bkp nogle forbehold for at "blotte" dataforespørgslen, man kan argumentere for at udviklerne i servicelaget SKAL kende til data eller som minimum være istand til at skrive Sql der performer korrekt, heldigvis er langt de fleste jo interesseret i at lave god kode, så jeg ser ikke problemet så stort :)
Og mjaaa.... "Hvis det er sådan det skal virke giver det meget god mening. men i nogle af de eksempler jeg har hentet, tilgå de alligevel datalaget i Præsentationen (Context klassen der er genereret af EF):-/"
Det er ikke lige måden at gøre det på, medmindre projektet er af en begrænset størrelse og man kender domænet helt præcist. Om ikke andet er det jo en god måde lige at banke en prototype sammen på :) Det virker jo fint og nogle gange skal man også passe på ikke at overgøre tingene bare fordi man kan, oftest er projekter jo styret udfra et eller andet overordnet design/ arkitektur og så skal tingene naturligvis løses korrekt.
Det hele begynder at give lidt mere mening, men jeg har stadig et par spørgsmål :-)
Hvis vi har nedenstående struktur:
Datalag ------- EF Repository (implementerede klasser)
Sevicelag --------- POCO klasser Repository (Interfaces) ?? Service klasser
GUI / UI --------
Du "newer" en "WeatherRepository" men din klasse i Repository'et hedder bare "Weather", er det en fejl, eller er "WeatherRepository" et interface?
Har du ikke undladt at lave interfaces af repository klasserne i servicelaget? Bruge du dine serviceklasser istedet, og lader dem tilgå datalaget uden om et interface?
Endnu engang mange tal for hjælpen og tålmodigheden :-)
Ja naturligvis... det var en typo, Weather skal være WeatherRepository :)
Mht. interfaces så bruger jeg kun interfaces når det tjener et formål, eks.vis polymorphism, mere generelle extensions osv. Men det kunne også være at kravet til softwaren skulle være bygget omkring løskoblede komponenter og så ville jeg bruge interfaces med DI eks.vis StructureMap. Det kunne være at man skulle kunne udskifte et Repository med et andet og så er idéen jo fin med interfaces. Hvis man skulle klemme et interface ind i ovenstående Repository, så kunne det være:
interface IRepository<T> where T : class
{ public IQueryable<T> Query (istedet for IQueryable<Forecast> Weather()) void Delete (T ent) void Update (T ent) }
Men så begynder det allerede at være generisk og man skal måske kende for meget til hvad T kan være, det er noget der skal gennemtænkes - man kan naturligvis holde sine entiteter i et specifikt namespace. Ellers skal man lave det mere specifikt, altså: interface IWeatherRepository<T> { public IQueryable<Forecast> Query (istedet for IQueryable<Forecast> Weather()) void Delete( Forecast ent) void Update( Forecast ent) }
Men så forsvinder idéen jo lidt med et interface og giver måske mere vedligeholdelse end gavn :) hvis du kan følge mig?
Har rodet rundt med det her, og kan godt se at det begynder at blive smart, men kan ikke helt få det skruet sammen. Det kunne vel også være genialt hvis implementeringen af Repository'et var generisk, da de jo nok komme til at ligne hinanden meget? men hvis så også interfacet er generisk, så begynder det at blive svært :-/
Det hele kan nu sagtens være generisk, men med generiske repositories oplever man pludselig også at alt data adgang skal strømlines og det er jo ikke altid tilfældet... Jeg mener det hænder ret ofte at man har brug for at kontrollere hvad ens repository gør, eks.vis entiteter som kræver specielle loadoptions og andet, det kan hurtigt blive svært at styre. Min erfaring er at man ikke nødvendigvis skal indføre en generisk tilgangsvinkel, bare fordi man kan.
pnr, som du jo nok kan se udfra min posts, så er jeg lidt mere... "jo jo , vi tager det som det kommer" og prøver at holde os så fleksible som muligt. Jeg har mange mange gange siddet på diverse projekter hvor man har måttet lave alverdens skumle trick for at overholde en arkitektur og alligevel er det endt op med en dårlig performance, senere når projektet endelig er launchet og der kommer ekstra features, så bliver vi tvunget til at lave sjove ting pga. tidspres + at kravet ikke var med fra starten, så spørger sig selv om det nu også var umagen værd at lave alle de her "contraints" for/ på systemet. Du må ikke opfatte det som at jeg ikke mener der skal være nogle grænser = ingen design patterns, jeg holder meget af design patterns, men de må aldrig låse udvikleren fast og gøre tingene unødigt komplicerede og give dårligere performance.
Jeg kan fortælle dig hvordan jeg gør, der er sikkert nogle der kan gøre det smartere, men lige idag har jeg eks.vis siddet med performancetuning af et linq udtryk.... det viste sig at det fyrede en sql af for hver subquery, hvilket jo giver en dårlig performance..
Linq-udtrykket lignende:
var result = repo.FooBar().Where(x => x.Active == true) .Select(x => new Dto.Foo() { Id = x.Id , StartDate = x.StartDate.ToString() , Shots = x.ShotValues.Select(y => new Dto.Shot() { Id = y.tEnt1.tEnt2.SomeId , Name = y.tEnt1.tEnt2.SomeName , ColorSettings = new Dto.ColorSettings() { Color = y.tEnt1.Color ?? 0 , Some ID= y.Ent1.SomeId , Number = y.tEnt1.Number
} }).ToList() });
Den resulterede rent faktisk i en query for hver række FooBar, hvis jeg nu skulle have fulgt et bestemt pattern for mit repository kunne jeg jo aldrig have gjort flg.:
Altså som du kan se er der smidt en DataLoadOptions ind som parameter, det ville jo være svært hvis man skulle overholde et generisk design som dikterede at min metodesignatur skulle være ens. (FORUDSAT man startede sit design uden DataLoadOptions)
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.