Академический Документы
Профессиональный Документы
Культура Документы
This is the first part of a discussion about how to better use Entity
Framework in a Domain-Driven Design application.
It seems that some have been argued about it, for exemple Julie Lerman
gives a good overview in this article EF Models with DDD Bounded
Contexts.
The central idea is breaking your application in smaller and more coese
contexts by defining a DbContext with fewer Entities. These Contexts are
not exactly the Bounded Contexts that Eric Evans describe in his book
Domain-Driven Design, however they offer the same focused
understanding about the Domain over a specific responsability inside the
application.
There is obviously another benefit with this approach. By defining a
smaller DbContext the metadata cache is also smaller and generated
faster the first time the context is needed.
Further in this article following parts I will present this Domain-Driven
organization in a more pratical way, explaining design patterns and
estructures adopted to achieve what I believe, is a good software solution
with Entity Framework.
Domain-Driven Design with Entity Framework (Part 2) : Layers
So far we have seen a small introduction of DDD with Entity Framework.
In this second part I will present an architectural overview of layers in a
DDD application and locate EF inside this architecture.
I will consider three layers (there can be a fourth that will be covered
later), Domain Layer, Infrastructure layer and Interface layer. Each one in
a distinct project, where I will call then:
App.Domain
App.Infrastructure
App.Interface
We must keep a relationship where Infrastructure and Interface depend on
Domain and Interface will also depend on Infrastructure.
namespace App.Infrastructure.Persistence.EntityFramework
{
namespace App.Infrastructure.Persistence.EntityFramework
{
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly DbSet<T> _dbSet;
public GenericRepository(DbSet<T> dbSet)
{
_dbSet = dbSet;
}
public IQueryable<T> AsQueryable()
{
return _dbSet.AsQueryable();
}
public IEnumerable<T> GetAll()
{
return _dbSet.AsEnumerable();
}
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
{
return _dbSet.Where(predicate);
}
public T Single(Expression<Func<T, bool>> predicate)
{
return _dbSet.Single(predicate);
}
public T SingleOrDefault(Expression<Func<T, bool>> predicate)
{
return _dbSet.SingleOrDefault(predicate);
}
public T First(Expression<Func<T, bool>> predicate)
{
return _dbSet.First(predicate);
}
public T FirstOrDefault(Expression<Func<T, bool>> predicate)
{
return _dbSet.FirstOrDefault(predicate);
}
public IQueryable<T> AsNoTrackingQueryable()
{
return _dbSet.AsNoTracking().AsQueryable();
}
namespace App.Infrastructure.Persistence.EntityFramework.Extensions
{
public static class StreetRepositoryExtension
{
public static List<Street> FindAllStreets(this IGenericRepository<Street>
repository)
{
return repository.GetAll().ToList();
}
public static Street FindStreetByName(this IGenericRepository<Street>
repository, string name)
{
return repository.SingleOrDefault(s => s.Name == name);
}
}
}
This is enough to inject things into other things. For exemple, if you are a
REST person, this is how to constructor inject in a Web API controller:
[RoutePrefix("address/city")]
public class CityController : ApiController
{
private readonly IAddressUnitOfWork _unitOfWork;
public CityController(IAddressUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
}
[Test]
public void GetByIdTest()
{
var uow = new MockAddressUnitOfWork();
uow.CityRepository.Add(new City {Id = 1, Name = "Sydney"});
var controller = new CityController(uow);
ControllerRequest.CreateRequest(controller);
HttpResponseMessage response = controller.GetById(1);
CityDTO cityDto = response.Content.ReadAsAsync<CityDTO>().Result;
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.AreEqual("Sydney", cityDto.Nome);
}
It's a very simple micro test to check if the REST API responds correctly
when calling the /address/city/1 url, and considering Sydney the city with
Id 1. We are obviously mocking a request and the databse to make it a
blazzing fast micro test, as it should be.
To conclude, we have seen that the DDD/EF topic covers many other
things. From design patters to Mocks we had a good overview of actualy
developing in real projects with the DDD/EF in mind. In the future I will
focus more in some of the important topics and going further in the
discussion.
For now, the Domain-Driven Design with Entity Framework post is
finally done.
EntityFrameworkandtheUnitof
Workpattern
PatrickDesjardinsASP.MVC,Enterprise,EntityFrameworkDec,16,
201321Comments
QuickAdsenseWordPressPlugin:http://quicksense.net/
Abstract
Thisarticleisasummaryofhowtomaketheuseofaunitofwork
withEntityFramework.Firstofall,EntityFrameworkisaunitof
workbyitself.Youcandomultipleinsert,updateanddeleteandits
notuntilaSaveChangesthateverythingiscommittedtotheSql
Server.Theproblemisthatyoumaywanttohavemultiple
repositories.Thismeanthatifyouwanttobeunderthesame
transactionthatyouwanttosharethesaveDbContext.Herecomes
theunitofwork,apatternthatsharetheDbContext.Thereference
ofDbContextissharedacrossrepositories,whichisinteresting
becauseifwewanttobedomaindrivenwecansharetheDbContext
betweenrepositoriesofthesamedomain.Itsalsointerestingfor
unittesting.Thereasonisthattheunitofworkhasinterfacewhich
canbeeasilymocked.
IhaveseenanarticleonAsp.NetwebsiteconcerningEntity
FrameworkandtheunitofworkpatternbutIbelieveitswrong.I
prefertheoneofJulieLermaninherPluralsightvideo.Themain
reasonistheoneofAsp.Netincludestherepositoryinsidetheunitof
workandtheDbContext.TheoneofJulieLermanonlycontainthe
DbContextandtheunitofworkispassedthrougheveryrepositories
ofthedomain.
Hereistherepresentationofeverylayersthatwewouldlikewiththe
unitofwork.
Asyoucansee,thecontrollershouldcontacttheservicelayerwhere
allqueriesarefromdatabases,accesstocachingservicesandweb
servicesareexecuted.Forthedatabasepart,wecontactthedata
accesslayeraccessorwhichisanabstractionfortheunitofworkand
repositories.Thisalloweverydevelopersthatuserepositoriesto
abstracttheneedtocreatetheunitofworkandtopassitthrough
constructors.Theaccessordoeshaveareferencetorepositoriesand
totheunitofwork.
Thisarticleexplainshowtocreatealayeredapproachthathasa
controller,aservicelayer,adataaccesslayeraccessorwith
repositoriesandunitofworkwithasimplesetofentities.Ialready
havewroteanarticleforrepositoryandentityframework.Thiswas
anothersimplerwaytodesigntherepository.Previously,afacade
waspassingtheDbContexttoallrepository,whichwascreatedthe
samebehaviorastheunitofworkpattern.However,theunitofwork
ismoreelaborateandallowstounittesteasilyandallowyouto
reuserepositoryinseveralDbContextifrequired.Havingthe
possibilitytocreateseveralDbContextandtoshareitbydomain(for
domaindrivendesign)isimportantforbigsoftware.Itincreasethe
performanceofthedatabasecontextbyhavingalimitedamountof
entitytohandle.So,thepreviouswaytohandlerepositoryisperfect
ifyouhaveunder50entities.Thisisaruleofthumbanditdepends
ofmanyfactors.Ifyouhavealotofentitiesandthatyoucandraw
specificdomains,theapproachofunitofworkinthispostis
preferable.Asyouwillsee,alotofmoreclasseswillbeneededand
thisisnotasmalldetailtoconsiderbeforegoingintothisway.
Creatingtheentities,thedatabasecontextand
thetables
Firstofall,letscreateentitiesandasimplecontextthatwewillcall
directlyfromthecontroller.Thisshouldneverbeendonein
enterprisebutitwillallowustomigratethecodefromasimple
basiccodetoamoreheavylayeredapplication.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
Wehavetwoclasses,oneisforCatandoneisforDog.Bothinherit
fromAnimalclass.Theseareverysimpleclassesbecausewewant
tofocusontheunitofworkandnotoncomplexclasses.Thenext
stepistocreatethedatabasecontext.
ThefirststepistogetEntityFramework.Thiscanbedonebyusing
Nugetwiththeinterface(ManageNugetPackage)orwitha
commandline:
?
Then,weneedtoinheritfromDbContextandsetupweb.configto
haveaconnectionstringforthedatabase.Theweb.configlookslike
this:
?
<
2
3
4
5
6
7
8
ThefirstisthatwehaveanewconfigSectionforEntityFramework.
Thishasbeenaddedautomatically.Thelinethatisrequiredtobe
addedmanuallyistheconnectionstring.
Thelaststepistoconfiguretheentity.Sincewearesimplifythe
wholeapplicationforthepurposeoftheunitofwork,themodel
classwillbedirectlytheentity.Somemaywantinenterprise
applicationhaveanadditionallayertonotshareentityclasseswith
themodel.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
modelBuilder.Entity<Animal>()
.HasMany(entity => entity.Enemies)
.WithMany(d => d.EnemyOf)
.Map(d => d.ToTable("Animals_Enemies_Association").Map
}
}
TheconfigurationhassomethingspecialfortheEnemieslistbecause
Ididnotwantedtohandlethetheassociationtablebymyself.Entity
Frameworkcanhandleitforusbyconfigureamanytomany
relationshipwiththeanimalclass.Itrequirestohaveatablename
forthemanymanytablewithaforeignkeys.
Setupthecontrollers,servicelayeranddata
accesslayer
Beforeevenhavingtheservicelayer,letsusethecontextdirectly
intothecontrollerandseethedatabasecreation.Then,wewill
changetocodeeverylayersbutnottheunitofworkyet.Wecanuse
scaffoldingtoleverageVisualStudiopowertogetcodegeneration
forus.Firststep,rightclickthecontrollerandselectaddnew
controller.
Thesecondstepistoselecttomodelclass,youcanselecttheoneof
AnimalandselecttheDbContextclass.Ifyoudonotseeyour
DbContextclass(DatabaseContext),closethewindowandcompile
yourapplication.Thewizardbasesitschoiceonthecompiled
resourceoftheproject.Oncegenerated,youcanexecutethecode,
IISExpressstartbydefaultandyoujustneedtogoto
http://localhost:15635/AnimalandtheDbContextwillstartthe
creationofthedatabase.IfyouopenSQLServerManager,theunit
ofworkdatabaseshouldhave3tables.
Transformingtohaveservicelayers
Atthisstage,thearchitectureofthewebapplicationisnotenterprise
grade.Thecontrollerhasastrongreferencetothedatabasecontext.
Thenextstepistohaveeverythingrelatedtothedatabaseinsidea
servicelayerwhichabstractentityframework.Thisallowustotest
easilythecontrollerwithouthavingtocareaboutthedatabase.
Thisisthecurrentcontrollercodeatthismoment.
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="Id,Name")] Animal ani
{
if (ModelState.IsValid)
{
db.Entry(animal).State = EntityState.Modified;
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
db.SaveChanges();
return RedirectToAction("Index");
}
return View(animal);
}
Ifyouwanttotestrapidlythedatabase,justaddthecodeinthe
index.
?
01
02
03
04
05
06
07
08
09
10
11
12
Thefirststepistocreatearepositoryclassforanimalinsidethe
DataAccessLayerfolder.Normally,Icreateafoldercalled
Repositorytohaveallrepositories.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
}
public void Insert(Models.Animal animal)
{
db.Animals.Add(animal);
db.SaveChanges();
}
public void Update(Models.Animal animal)
{
db.Entry(animal).State = EntityState.Modified;
db.SaveChanges();
}
public void Delete(Models.Animal animal)
{
db.Animals.Remove(animal);
db.SaveChanges();
}
public void Dispose()
{
db.Dispose();
}
public IList<Animal> GetAll()
{
return db.Animals.AsNoTracking().ToList();
}
}
Thisclassasalsoaninterfacewiththepublicmethodinit.
Thesecondstepistocreateaservicelayer.Normally,wewould
createanewproject,buttokeepeverythingsimple,letsjustadda
newfolder(namespace).Then,wemovetheDatabaseContextclass
fromthecontrollertotheservice.
Theanimalserviceclasslookslikethefollowingcode.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
this.animalRepository = animalRepository;
}
public Models.Animal Find(int? id)
{
return this.animalRepository.Find(id);
}
public void Insert(Models.Animal animal)
{
this.animalRepository.Insert(animal);
}
public void Update(Models.Animal animal)
{
this.animalRepository.Update(animal);
}
public void Delete(Models.Animal animal)
{
this.animalRepository.Delete(animal);
}
public IList<Animal> GetAll()
{
return this.animalRepository.GetAll();
}
}
Itsallthecodefromthecontroller.Later,someimprovement
shouldbedone.OneofthischangeistomovetheSaveChanges
becauseitsnotinterestingtosaveeverytimeweadd,modifyor
updateanentity.Thiscauseperformanceproblemwhenseveral
entitiesarerequiredtobepostedtothedatabase.However,lets
focusonthetransformationfirst,laterthesedetailswillbegone.The
roleoftheservicelayeristoresembleeveryrepository.Inthis
situationwehaveonlyonerepository.Infact,inmorecomplex
problemlikeinenterprise,aservicehasseveralrepositoryand
cachingclasses.
Thenextclassthatrequirechangesistheanimalcontrollerclass.
ThisonenowhasaconstructorthatneedanIAnimalService.
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="Id,Name")] Animal ani
{
if (ModelState.IsValid)
{
_service.Update(animal);
return RedirectToAction("Index");
}
return View(animal);
}
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
}
Animal animal = _service.Find(id);
if (animal == null)
{
return HttpNotFound();
}
return View(animal);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Animal animal = _service.Find(id);
_service.Delete(animal);
return RedirectToAction("Index");
}
}
Atthisstage,thecontrollerisseparatedfromthedatabasebythe
serviceandtherepository.Still,itsbettertonothavingastrong
referencetoAnimalServiceinsidethecontroller.Thisiswhywewill
extractaninterfacefromAnimalServiceandwewillinjectthe
concreteclassbyinversionofcontrol.Thisallowustohavewhen
doingtestentrypointtoinjectafakeAnimalServicethatwontgoes
tothedatabase.Youcanusetherefactoringtooltoextractthe
interfaceeasily.
1
2
3
4
5
6
7
8
Insidethecontroller,wehavetwoconstructors.Onetohelpusfor
thisexamplewhichinstantiatetheservicelayerandtherealonethat
takesasingleparameter.Thisistheonethatyoushouldhaveinyour
enterprisegradesoftwarebecauseitcaninjectanythingof
IAnimalServiceintothecontroller.
?
1
2
3
4
5
6
7
8
9
Beforeimplementingtheunitofwork,wewillcreateanew
repositorytoillustratewhytheunitofworkisrequired.Wewillalso
doalittlerefactoringbychangingtherepositorytostophaving
automaticallyacalltoSaveChanges.Thisallowustoinsertseveral
entitiesinasingletransaction.
Thisisnowtheanimalserviceclassandinterface.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
}
public IList<Animal> GetAll()
{
return this.animalRepository.GetAll();
}
public void Save(Animal animal)
{
Save(new List<Animal> { animal });
}
public void Save(IList<Animal> animals)
{
foreach (var animal in animals)
{
if (animal.Id == default(int))
{
this.animalRepository.Insert(animal);
}
else
{
this.animalRepository.Update(animal);
}
}
this.animalRepository.Save();
}
}
Asyoucansee,itsbetter.Italsohidethecomplexityforupdateand
insertbyhavingasinglemethodsave.Next,wewillcreateanew
repository.Wewontcodeitsdetailbutwewilluseitinsidethe
AnimalServicetosimulateacasewhereweneedtointeracton
severalentities.
?
1
2
3
4
5
6
7
Wealsoneedtomodifytheservicetohaveinitsconstructorthe
IHumanRepository.
?
01
02
03
04
05
06
07
08
09
10
11
12
Thenwecansimulatetheneedtohavesomethinginthesame
transactionbetweenanimalandhumanrepository.Thiscanbeinthe
SavemethodoftheAnimalService.Letscreateanewsavemethod
intheservicewhichtakeanAnimalandalsoanHuman.In
IAnimalServiceweadd.
?
Andintheconcreteimplementationwehave:
?
1
2
3
4
5
Thisiswheretheunitofworkisrequired.Theanimalrepositoryhas
itsownDbContextandthehumanrepositoryhasitsonetwo.Since
bothhavenotthesamerepository,theyareintwodifferent
transaction.WecouldwrapthesebothlineswithaTransactionScope
butsinceEntityFrameworkisalreadyatransactionscopeandsince
inmorecomplexscenariowherewewouldwanttousethe
DbContextfurthermore,havingtousethesameDbContextis
somethingviable.
ImplementingUnitofWorkpattern
Aswehaveseen,weneedtosharetheDbContext.Thisiswherethe
unitofworkshines.Thefirstmoveistocreatetheunitofwork
whichholdtheDbContext.
?
1
2
3
4
5
6
7
8
Theinterfacecouldbericherbutthisshouldbetheminimalnumber
ofmethods.Theimplementationisonlyhavingacentralpointfor
everydatabasesets.Inamoredomaindrivendesignapplicationwe
couldrestrainentitiesbyhavingaDbContextthatislessgeneral
thantheonecreated.AllDomainContextcontainsallentitiesset.
Thisisperfecttocreatethewholedatabaseorwhenyourapplication
hasalimitednumberofentities(under50).Butifyouaredomain
drivendesignorwithabigapplication,tohaveEntityFramework
performwellandrestrictthedomains,havingseveralDbContextisa
goodsolution.WithunitofworkanditsgenericTclass,youcan
passanydomainyouwanttohave.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
return DatabaseContext.Entry<T>(entity);
}
}
ThisunitofworkisverygeneralsinceitcantakesTasset.This
meanthatanyentitydefinedcanbeused.Inourexample,withthis
modifiedunitofwork,thecontrollerneedstobechangedtoo.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public AnimalController()
{
var uow = new UnitOfWork<AllDomainContext>();
_service = new AnimalService(uow, new AnimalRepository(u
}
public AnimalController(IAnimalService animalService)
{
_service = animalService;
}
//...
}
So,theunitofworkisinstantiatedwiththedomainwewant.Here,
itseverything.Westillhavetherealconstructorthattakesonly
theIAnimalServicewhichistheonethatshouldbeusedinthereal
applicationwithinversionofcontroltoinjectthecontroller.Since
itsanarticle,tokeepitsimple,IshowyouwhattheIoCshoulddo
inthebackground.
Theanimalserviceischangedtootoworkwiththeunitofwork.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
}
}
this.unitOfWork.SaveChanges();
}
public void SaveAll(Animal animal, Human humain)
{
this.animalRepository.Insert(animal);
this.humanRepository.Insert(humain);
this.unitOfWork.SaveChanges();
}
}
Therepositorynowacceptstheunitofwork.Itcanworkswithset
definedinthedomainwithoutproblem.
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
UnitOfWork.Set<Animal>().Remove(animal);
}
public IList<Animal> GetAll()
{
return UnitOfWork.Set<Animal>().AsNoTracking().ToList();
}
}
ItspossibletocontinuetoimprovetheunitofworkandEntity
Frameworkbygoingfurtherintheuseoftherepository.But,what
havebeenshownhereisenterprisegradedrepositorydesign.It
allowsyoutodividethedomainandimprovetheperformanceof
EntityFrameworkbythesametime.Itallowstohaveanabstraction
betweentheAsp.NetMVCfrontandtheEntityFramework.Iteasily
testablebecauseweuseinterfacewhichcanbemockedeasily.
Benefitsareclearbutthepricetopayistheoverwhelmrequiredto
supportthisinfrastructure.Moreclassesneedtobeinplace.Still,the
versionpresentedislightandoncethesetupisdone,addingnew
entityisonlyamatterofeditingthecontextinwhichitbelongsand
createintotherepositorywhatactionisneeded.