entity framework in a multi layered application.docx
TRANSCRIPT
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
1/19
ENTITY FRAMEWORK IN A MULTI
LAYERED APPLICATIONIn this blog I'll show you how you can use Entity Frameworkin an MVC.Net website, with a multi layered architecture.The main goals o the architecture to make unit testing andintegration testing easy, and kee! a clean se!eartion oconcerns. To achie"e this goal I will be using the conce!t o#e!endency In$ection %#I& and uto ac als IoC Container (#I ramework. ). IntroductionLet me start by introducing the layered architecture that I like touse for my web applications:
Database to store data. Data Access layer which contains the linq queries that are executed
against ntity !ramework. Domain "er#ices layer$ which holds the business logic and workflow
logic. %&'.(et website which talks only to the Domain "er#ices layer. )'! ser#ices which talk only to the Domain "er#ices layer.
*his isn+t an uncommon approach. %ain ad#antages are cleanseperation of layers and easy reuse of domain logic by the %&'.(etwebsite and the )'! ser#ices ,and windows ser#ices if you like-.*. +etting u! the solution *.) #ata ccess ayerLet+s start with creating the data access layer. In this example weha#e two entities:
public class mployee public #irtual int Id get/ set/ 0 public #irtual string (ame get/ set/ 0 public #irtual double "alary get/ set/ 0 public #irtual Date*ime1 2ob ndDate get/ set/ 0 public #irtual 3rganisation 3rganisation get/ set/ 0 0 public class 3rganisation public #irtual int Id get/ set/ 0 public #irtual string (ame get/ set/ 0 public #irtual List4 mployee5 mployees get/ set/ 0 0
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
2/19
(ow we can add the entity framework context. I use 'ode !irst$ sothis is the context: public class Database'ontext : Db'ontext public Db"et4 mployee5 mployees get/ set/ 0 public Db"et43rganisation 5 3rganisations get/ set/ 0 0 (ext we add two Data Access classess$ which contain the linqqueries on top of the ntity !ramework context: public class 3rganisationDa public 3rganisation 6et7yId,int id- #ar ctx 8 new Database'ontext ,-/ return ctx.3rganisations."ingle,it 85 it.Id 88 id-/ 0 0 public class mployeeDa public #oid Add, mployee employee-
#ar ctx 8 new Database'ontext ,-/ ctx. mployees.Add,employee-/ ctx."a#e'hanges,-/ 0 0 *o summari9e$ this is what the DataAccess pro ect looks like now:
)e ha#e a Database'ontext$ an mployee entity$ an mployeeDataAccess class$ an 3rganisation entity and an 3rganisationDataAccess class.
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
3/19
*.* #omain +er"ices ayer )e can start to build the Domain "er#ices Layer$ and use theDataAccess classes: public class mployee"er#ice public #oid Add mployee,string name$ int organisationId- #ar organisation 8 new3rganisationDa,-.6et7yId,organisationId-/
#ar employee 8 new mployee,-/ employee.(ame 8 ;2ohn;/ employee.3rganisation 8 organisation/
#ar employeeDa 8 new mployeeDa,-/ employeeDa.Add,employee-/ 00 As you can see we create a new employee and connect that to anexisting organisation$ (ext we can use this ser#ice in an %&'.(etpro ect. *.- MVC ayer public class
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
4/19
-. First !roblem Multi!le Entity Frameworkconte/ts-.) IntroductionIf we would run the solution we created and the Index method of
the
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
5/19
public 3rganisationDa,Database'ontext databasecontext- ?databasecontext 8 databasecontext/ 0
public 3rganisation 6et7yId,int id- return ?databasecontext.3rganisations."ingle,it 85 it.Id 88id-/ 0 0 public class mployeeDa pri#ate Database'ontext ?databasecontext/
public mployeeDa,Database'ontext databasecontext- ?databasecontext 8 databasecontext/ 0
public mployee 6et7yId,int id- return ?databasecontext. mployees."ingle,it 85 it.Id 88 id-/ 0
public #oid Add, mployee employee-
?databasecontext. mployees.Add,employee-/ ?databasecontext."a#e'hanges,-/ 0 0 If we run the solution now$ and execute the Index method of the
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
6/19
0. +econd !roblem Not 1nit Testable0.) Introduction3ur Domain "er#ices can+t be unit tested$ but only integration testscan be written$ because it uses the data access layer and the
database. Let+s introduce a new method ,6i#e=aise- in the mployee"er#ice$that we went to test: public class mployee"er#ice public #oid 6i#e=aise,int employeeId$ double raise- #ar database'ontext 8 new Database'ontext,-/
#ar employeeDa 8 new mployeeDa,database'ontext-/ #ar employee 8 employeeDa.6et7yId,employeeId-/
if ,employee.2ob ndDate.
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
7/19
database. "o this test can+t be run. And e#en if there+s a database$we don+t want to test the database$ we want to test the logic insidethe Domain "er#ice.0.* +olution one In Memory #atabaseIf we don+t want to use a real database$ we can use an inFmemorydatabase. )e ha#e to change the code of the mployee"er#icea bit: public class mployee"er#ice Database'ontext ?database'ontext/
public mployee"er#ice,Database'ontext database'ontext- ?database'ontext 8 database'ontext/ 0
public #oid 6i#e=aise,int employeeId$ double raise- #ar employeeDa 8 new mployeeDa,?database'ontext-/ #ar employee 8 employeeDa.6et7yId,employeeId-/
if ,employee.2ob ndDate.
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
8/19
#ar working mployee 8 new mployee,- Id 8 > 0/
database'ontext. mployees.Add,working mployee-/
database'ontext."a#e'hanges,-/
#ar employee"er#ice 8 new mployee"er#ice,context-/
Act #ar employee 8 employee"er#ice.6i#e=aise,>$ >EE-/
Assert Assert.Are qual,>EE$ employee."alary-/0.- +olution two Mock ob$ectsIf we don+t want to do anything with the DataAccess layer$ we canuse a fake DataAccess Layer$ that doesn+t need a database but ustreturns an employee.
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
9/19
public #oid Add, mployee employee- ?database'ontext. mployees.Add,employee-/ ?database'ontext."a#e'hanges,-/ 0 0 (ow we can create a mock of I mployeeDa in the test: #ar employeeDa%ock 8 new %ock4I mployeeDa5,-/ employeeDa%ock ."etup4 mployee5,it 85 it.6et7yId,>-- .=eturns,new mployee,- Id 8 >$ "alary 8 E 0-/ #ar employeeDa 8 employeeDa%ock.3b ect/ "o how do we use this mocked mployeeDa in the
mployee"er#ice )e can in ect it in the constructor$ assign it to apri#ate field$ and use it in the methods like this: public class mployee"er#ice Database'ontext ?database'ontext/ I mployeeDa ?employeeDa/
public mployee"er#ice,Database'ontext database'ontext$
I mployeeDa employeeDa- ?database'ontext 8 database'ontext/ ?employeeDa 8 employeeDa/ 0
public mployee 6i#e=aise,int employeeId$ double raise-
#ar employee 8 ?employeeDa.6et7yId,employeeId-/ if ,employee.2ob ndDate.
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
10/19
return employee/
0 0 *o conclude this chapter$ this is the complete test:B*est%ethodC public #oid )orking mployee'an6et=aise,- Arrange #ar context 8 new Database'ontext,-/
#ar employeeDa%ock 8 new %ock4I mployeeDa5,-/ employeeDa%ock ."etup4 mployee5,it 85 it.6et7yId,>-- .=eturns,new mployee,- Id 8 >$ "alary 8 E 0-/
#ar employeeDa 8 employeeDa%ock.3b ect/
#ar employee"er#ice 8 new mployee"er#ice,context$employeeDa-/
Act #ar employee 8 employee"er#ice.6i#e=aise,>$ >EE-/
Assert Assert.Are qual,>EE$ employee."alary-/
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
11/19
2. Third !roblem 1nwanted EF 3ueries in#omain +er"ices2.) Introduction"ince the Domain "er#ices ha#e access to the ntity !ramework
context$ it+s possible to write queries to directly load entities fromthe database. *his goes against the "ingle =esponsibility Grincipleand "eperation of 'oncerns. It+s possible to do this in the mployee"er#ice: public mployee 6i#e=aise,int employeeId$ double raise- #ar employee 8 ?database'ontext. mployees .Include,;3rganisation;-
."ingle,it85it.Id 8 employeeId-/
... 0 )hat+s wrong about this1 *he main problem is that once we startthis way$ soon a lot of methods in the Domain "er#ices Layer willha#e queries on top of ntity !ramework.
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
12/19
0 0 Hse the Hnit3f)ork in the Domain "er#ices Layer this way: public class mployee"er#ice : I mployee"er#ice Hnit3f)ork ?unit3f)ork/ I mployeeDa ?employeeDa/ I3rganisationDa ?organisationDa/
public mployee"er#ice,Hnit3f)ork unit3f)ork$ I mployeeDa employeeDa$ I3rganisationDa organisationDa- ?unit3f)ork 8 unit3f)ork/ ?employeeDa 8 employeeDa/ ?organisationDa 8 organisationDa/ 0
public mployee 6i#e=aise,int employeeId$ double raise- #ar employee 8 ?employeeDa.6et7yId,employeeId-/
if ,employee.2ob ndDate.
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
13/19
)hen we go back to the the %&'.(et pro ect )eb Layer$ we ha#eto write this code to gi#e an employee a raise: public class
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
14/19
organisationDa-/ 0
public Action=esult Index,- ?employee"er#ice.6i#e=aise,>$ >EE-/
return &iew,-/ 0 0 *here are two things nice about the code abo#e: >. *he index,- method doesn+t ha#e to worry about initialising the
mployee"er#ice anymore/ . If we want to test the Index,- method$ we can in ect theDatabase'ontext$ so we can use for example an in memorydatabase. 7ut there are also a few things that aren+t so great: >. )e ha#e access to the Database'ontext and can write directqueries on top of ntity !ramework/ . )e can+t mock the mployee"er#ice/ J. If the mployee"er#ice needs another DataAccess class$ weha#e to add it in the constructor of the controller too. *he morecontrollers there are$ the more work this is. *his quickly gets pretty
tedious. *herefore$ let+s look at part two of the solution6.- +olution two 1se an IoC containerIo' containers are used to gi#e control o#er instantiating ob ect toan external framework. Kou set them up once$ and the create andin ect the ob ect e#erytime it is needed. In the
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
15/19
#ar builder 8 new Autofac.'ontainer7uilder,-/
builder.=egister'ontrollers,typeof,%#cApplication-.Assembly-.GropertiesAutowired,-/
BInsert custom initialisation hereC
#ar container 8 builder.7uild,-/
Dependency=esol#er."et=esol#er,newAutofacDependency=esol#er,container--/ 0 As Autofac is setup$ we can start to register our "er#ices andDataAccess classes. I like to do that in a new pro ect$ to keep the)eb layer clean of all references that are not neccessary. I namethis pro ect AutofacInitialiser. !irst we register the DataAcces classes namespace Mample.AutofacInitialiser public class DataAccess%odule : Autofac.%odule
protected o#erride #oid Load,'ontainer7uilder builder- builder.=egisterAssembly*ypes,Assembly.Load,;Mmpl.DataAccess;-- .)here,t 85 t.(ame. nds)ith,;Da;-- .AsImplementedInterfaces,- .InstanceGerLifetime"cope,-/ 0 0
0 And register this module in the global.asax: protected #oid Application?"tart,- ...
#ar builder 8 new Autofac.'ontainer7uilder,-/
builder.=egister'ontrollers,typeof,%#cApplication-.Assembly-.Groper
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
16/19
tiesAutowired,-/
builder.=egister%odule,new DataAccess%odule,--/
#ar container 8 builder.7uild,-/
Dependency=esol#er."et=esol#er,newAutofacDependency=esol#er,container--/ 0 )hat did we ust do1 )e+#e created a %odule that scans theDataAccess Layer and registers e#ery class the ends with Da inAutofac. *his means that if we write 3rderDa$ or GroductDa$ orwhate#erDa$ they are registered automatically with autofac$ withoutwriting any special code. )e can write a %odule for the Domain "er#ices too: public class Domain"er#ices%odule : Autofac.%odule
protected o#erride #oid Load,'ontainer7uilder builder-
builder.=egisterAssembly*ypes,Assembly.Load,;Mmpl.Domain"er#ices;--
.)here,t 85 t.(ame. nds)ith,;"er#ice;--
.AsImplementedInterfaces,-
.InstanceGerLifetime"cope,-/ 0
0 and register it in the global.asax: protected #oid Application?"tart,-
...
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
17/19
#ar builder 8 new Autofac.'ontainer7uilder,-/
builder.=egister'ontrollers,typeof,%#cApplication-.Assembly-.GropertiesAutowired,-/
builder.=egister%odule,new DataAccess%odule,--/ builder.=egister%odule,new Domain"er#ices%odule,--/
#ar container 8 builder.7uild,-/
Dependency=esol#er."et=esol#er,newAutofacDependency=esol#er,container--/ 0 And finally we write a module for all ntity !ramework relatedclasses: public class ntity!ramework%odule : Autofac.%odule
protected o#erride #oid Load,'ontainer7uilder builder-
builder.=egister%odule,new DataAccess%odule,--/
builder.=egister*ype4Database'ontext5,-
.As"elf,-
.InstanceGerLifetime"cope,-/
builder.=egister*ype4Hnit3f)ork5,- .As"elf,-
.InstanceGerLifetime"cope,-/
0
0
and register it in the global.asax:
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
18/19
protected #oid Application?"tart,- ...
#ar builder 8 new Autofac.'ontainer7uilder,-/
builder.=egister'ontrollers,typeof,%#cApplication-.Assembly-.GropertiesAutowired,-/
builder.=egister%odule,new DataAccess%odule,--/ builder.=egister%odule,new Domain"er#ice%odule,--/ builder.=egister%odule,new ntity!ramework%odule,--/
#ar container 8 builder.7uild,-/
Dependency=esol#er."et=esol#er,newAutofacDependency=esol#er,container--/ 0 (ow we can in ect the mployee"er#ice into the
-
8/9/2019 ENTITY FRAMEWORK IN A MULTI LAYERED APPLICATION.docx
19/19
>. )e ha#e no access to the Database'ontext from the )eb Layer$and can+t write direct queries on top of ntity !ramework/ . )e can mock the mployee"er#ice/ J. If the mployee"er#ice needs another DataAccess class$ wedon+t ha#e to add it in the constructor of the controller anymore$and don+t ha#e to add anything to the Autofac inisialisation code. 7 Final thoughtsIn this blog I wrote how to create a &isual "tudio solution with threelayers ,Data Access$ Domain "er#ices and )eb- which are clearlyseperated from each other$ and are easy to test. I+#e achie#ed thisby wrapping the ntity !ramework context and by using Autofac asIo' container.