Вы находитесь на странице: 1из 35

Domain-Driven Design with Entity Framework (Part 1)

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.

Describing the three layers:


Domain: The application core. Business rules are defined and contexts
are created here. It must not depend on any other application and be
responsible exclusively for business. Entities, Value Objects, Domain
Events and Domain Services can be found here.
Infrastructure: Where exterior but essencial features for the domain, like
persistence logic, will be. Also Integration with remote systems should be
put in here. In this layer we must always consider patterns that act as an
anti-corruption layer to the domain. Adapters and Repositories are
common solutions.
Interface: This is where the domain is made available to the other sistems.
It can be accomplished through REST services or a MVC application.
So, now that we have a definition of layers, where does EF comes in?
First we must consider Entity Framework code-first approach as the most
appropriated for this task, since it enable the definition of our Entities as
POCO's in the Domain layer. This is essencial because the Domain layer
doesn't depend on any other layer, but the persistence will depend on the
Entities inside the Domain.
Next let's make the Infrastructure layer as the Entity Framework home. In
the Infrastructure I will create the DbContext, and map the entities
corresponding to each context inside it.
Here there is a small catch. The DbContext class is known to be a Unit of
Work implementation (see martin fowler definition), so I'm going to call
my context's UnitOfWork. For example:

namespace App.Infrastructure.Persistence.EntityFramework
{

public class AddressUnitOfWork : DbContext


{
public AddressUnitOfWork(string conn) : base(conn)
{
}
public DbSet<Street> Streets { get; set; }
public void Commit()
{
SaveChanges();
}
}
}

As we can see in the example a Unit Of Work is created considering the


Street Entity as part of this context. This entity is defined in the Domain
layer as POCO and once it is mapped inside de DbContex by passing the
class to the DbSet our entity can be persisted.
I'm not showing the Street Entity, but you can figure out what properties
it may contain.
Now we have a domain with a Entity called Street that is persistence
ignorant and an Address Context inside the Infrastructure mapping that
Entity to the persistency.
In my next Post I will define a Repository pattern inside of our Unit of
Work by creating a GenericRepository based on DbSet. Then we will see
how to interact with the Repository and implement finds with LINQ to
Entities.
Domain-Driven Design with Entity Framework (Part 3): Repository
In our last part we defined layers and a unit of work based on a
DbContext. Now it is time to write a Generic Repository.
A Repository must be able to isolate our domain objects from all the logic
needed to query the database (as stated by Martin Fowler). It will contain
the data mapper layer and an expression builder. Obviously, we don't
have to build all this patters from scratch since Entity Framework have
gently provided then.
So, we start by choosing Entity Framework DbSet object as the core class
for our Repository. It is both a mapper for a given Entity and a expression
builder by implementing the LINQ API.

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();
}

public void Add(T entity)


{
_dbSet.Add(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public void Attach(T entity)
{
_dbSet.Attach(entity);
}
}
}

Here we have the GenericRepository class defined with generics. Inside,


DbSet is a private instance property created on construction that provides
the LINQ interface methods for all queries we need.
There is also IGenericRepository Interface, that is quite helpfull in case
you want to inject Repositories into services or controllers.
The next step is adding repositories inside the unit of work reponsible for
the entities acting as aggregation root. Remember, according to DDD
when an aggregation root access the persistence layer it's done throgh a
Repository.
namespace App.Infrastructure.Persistence.EntityFramework
{
public class AddressUnitOfWork : DbContext
{
private readonly IGenericRepository<Street> _streetRepository;
private readonly IGenericRepository<City> _cityRepository;
public DbSet<Street> Streets { get; set; }
public DbSet<City> Cities { get; set; }
public IGenericRepository<Street> StreetRepository
{
get { return _streetRepository; }
}
public IGenericRepository<Cities> CityRepository
{
get { return _cityRepository; }
}
public AddressUnitOfWork(string conn) : base(conn)
{

_streetRepository = new GenericRepository<Street>(Streets);


_cityRepository = new GenericRepository<City>(Cities);
}
public void Commit()
{
SaveChanges();
}
}
}

As we can see a GenericRepository is instantiated on Unit of work


construction for each Entity that will be an aggrerate root. In our example
Street and City are entities that need their Repositories.
Accessing our Repositories is straight forward:
AddressUnitOfWork uow = new AddressUnitOfWork("connString");
uow.StreetRepository.GetAll();
uow.StreetRepository.AsQueryable().Where(s => s.name == "Bennett");
But there is in fact a little consideration here. If we need to keep query
logic inside the repository, by exposing the LINQ interface through the
IQueriable interface we might be doing it wrong.
A nice approach is using extension methods to solve this problem. But
this is a topic for our next part in this DDD/EF article, where we will see
how to create extension methods to keep our LINQ to Entities queries
inside the Repositories.

Domain-Driven Design with Entity Framework (Part 4): Extensions


In our last talk we learned how to design Generic Repositories based on
Entity Framework DbSet and LINQ to Entities. However, in the end it
looked excessive exposed with the IQuerable interface fully accessible.
Also, the best aproach when defining Repositories is being able to define
find methods clearly. It means that each Repository must have a way to
add custom methods even though they are essentially generic.
A very interesting feature that C# provide in these situations is Extension
Methods. By using it we basically open up the possibility of adding
methods to a generic interface without changing it. Lets see it in action.

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);
}
}
}

Here we define a Extension class for the StreetGenericRepository and add


two methods into it. The first is a findAll, the second a Find to streets by
a given name.
An important detail here is that the FindByName method calls internally
the SingleOrDefault method from the GenericRepository. It means that
the Repository knows that names are unique for Streeet and any Street
search made by name will consider this information regardless of who
calls it.
That case is exactly what we want to guarantee by defining a customized
interface to repositories. By keeping queries rules inside the repository
we gain reusability and cohesiveness.
Here is how calling our Repository should look like now:
AddressUnitOfWork uow = new AddressUnitOfWork("connString");
uow.StreetRepository.FindAllStreets();
uow.StreetRepository.FindStreetByName("Bennett");
It is definitely more clear and reusable that before.
Our next part will be a summary of what we have accomplished until now
and an overview of other good pratices to consider when working with
DDD/EF.

Domain-Driven Design with Entity Framework (Part 5): Conclusion

After a long halt I am back to conclude our discussion on EF/DDD. As


stated in our last part we will have some final words and a few interesting
points added to this topic.
We are now set with a nice Unit of Work derivated from EF's DbContext.
Also, we composed that Unit of Work with Generic Repositories based on
LINQ (as the Expression builder) and EF's DbSet (as the DB Mapper).
Later we created more organized Find methods by writing some nice
Extension methods for the Generic Repositories.
With that formulae we might have everything we need to work in a DDD
project with Entity Framework. But there are obviously minor details
missing.
First Missing Detail: Dependency Injection
Well, DI is cool, but also essencial to hide implementation in a DDD
application. Basicaly what we want is being able to inject our Unit of
Work inside the Interface layer (see part 2).
I'm not going into the DI Containers discusion or how to implement then
inside your application. I'm sure there are plenty of sources in the internet
talking about it. I have used NInject and StructureMap in the past, but
there are numerous others available. Pick you choice.
What matters here is that we need to write interfaces to Unit of Work and
Generic Repositories. No interfaces, no DI. So let's try it:
namespace App.Infrastructure.Persistence.EntityFramework
{
public interface IAddressUnitOfWork
{
IGenericRepository<Street> StreetRepository { get; };
IGenericRepository<City> CityRepository { get; };
}
}

Our Unit of work interface is quite simple right?


And every time we need another Repostory just remember to add it here
first to implement it in the Unit of Work. The IGenericRepository
interface I talked about in part 3, but had not shown it. Here it is:
public interface IGenericRepository<T> where T : class
{
IQueryable<T> AsQueryable();
IEnumerable<T> GetAll();
IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
T Single(Expression<Func<T, bool>> predicate);
T SingleOrDefault(Expression<Func<T, bool>> predicate);
T First(Expression<Func<T, bool>> predicate);

void Add(T entity);


void Delete(T entity);
void Attach(T entity);
T FirstOrDefault(Expression<Func<T, bool>> predicate);
IQueryable<T> AsNoTrackingQueryable();
}

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;
}
}

Second Missing Detail : Mocking and Testing


We consider ourselves all very good developers, therefore we all know
and love test driven developement. This is why I'm going to show you
how to properly mock your Unit of Work and Repositories.
Half the work was done in the previous topic. Yes, you needed that
interfaces to Mock your Unit of Work and Generic Repository.
First I will mock the Generic Repository. In this case I'm going to replace
the DbSet with a simple List (You might use a Dictionary or any other
IQueryable implementation in ICollection):
public class MockGenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly List<T> _objectList;
public MockGenericRepository(List<T> dictionary)
{
_objectList = dictionary;
}
public IQueryable<T> AsQueryable()
{
return _objectList.AsQueryable();
}
public IEnumerable<T> GetAll()
{
return _objectList.AsEnumerable();
}

public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)


{
return _objectList.AsQueryable().Where(predicate);
}
public T Single(Expression<Func<T, bool>> predicate)
{
return _objectList.AsQueryable().Single(predicate);
}
public T SingleOrDefault(Expression<Func<T, bool>> predicate)
{
return _objectList.AsQueryable().SingleOrDefault(predicate);
}
public T First(Expression<Func<T, bool>> predicate)
{
return _objectList.AsQueryable().First(predicate);
}
public T FirstOrDefault(Expression<Func<T, bool>> predicate)
{
return _objectList.AsQueryable().FirstOrDefault(predicate);
}
public IQueryable<T> AsNoTrackingQueryable()
{
return _objectList.AsQueryable();
}
public void AddWithGeneratedId(T entity)
{
_objectList.Add(entity);
}
public void Add(T entity)
{
_objectList.Add(entity);
}
public void Delete(T entity)
{
_objectList.Remove(entity);
}
public void Attach(T entity)
{
_objectList.Add(entity);
}

As you can see, LINQ made it really easy.


Now for the Mock Unit of Work, we are basicaly doing the same thing
done in the original Unit of Work. But this time not derivating from
DbContext, and insted of declaring a DbSet we declare the list we will
send to the MockGeneric Repository:
public class MockAddressUnitOfWork : IAddressUnitOfWork
{
private readonly IGenericRepository<Street> _streetRepository;
private readonly IGenericRepository<City> _cityRepository;
public IGenericRepository<Street> StreetRepository
{
get { return _streetRepository; }
}
public IGenericRepository<Cities> CityRepository
{
get { return _cityRepository; }
}
public MockAddressUnitOfWork()
{
_streetRepository = new MockGenericRepository<Street>(new
List<Street>());
_cityRepository = new MockGenericRepository<City>(new List<Cities>());
}
public void Commit()
{
// Do nothing in this mock
}
}

Interesting right? We are simply mocking the EntityFramework


implementation with a List in memory.
Now we can test, for exemple, a Web API controller implemented with
constructor DI like this:

[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

public class Animal


{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Animal> Enemies { get; set; }
public virtual ICollection<Animal> EnemyOf { get; set; }
}
public class Cat : Animal
{
public int NumberOfMustache { get; set; }
public int RemainingLife{get;set;}
}
public class Dog : Animal
{
public string Type { get; set; }
}

Wehavetwoclasses,oneisforCatandoneisforDog.Bothinherit
fromAnimalclass.Theseareverysimpleclassesbecausewewant
tofocusontheunitofworkandnotoncomplexclasses.Thenext
stepistocreatethedatabasecontext.
ThefirststepistogetEntityFramework.Thiscanbedonebyusing
Nugetwiththeinterface(ManageNugetPackage)orwitha
commandline:
?

PM> install-package entityframework

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

public class AllDomainContext:DbContext


{
public AllDomainContext():base("EntityConnectionString")
{
}

protected override void OnModelCreating(DbModelBuilder mo


{
base.OnModelCreating(modelBuilder);
//Table per type configuration
modelBuilder.Entity<Dog>().ToTable("Animals");
modelBuilder.Entity<Dog>().ToTable("Dogs");
modelBuilder.Entity<Cat>().ToTable("Cats");
//Primary keys configuration
modelBuilder.Entity<Animal>().HasKey(k => k.Id);

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

public class AnimalController : Controller


{
private DatabaseContext db = new DatabaseContext();
public ActionResult Index()
{
return View(db.Animals.ToList());
}

public ActionResult Details(int? id)


{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRe
}
Animal animal = db.Animals.Find(id);
if (animal == null)
{
return HttpNotFound();
}
return View(animal);
}

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

public ActionResult Create()


{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Id,Name")] Animal
{
if (ModelState.IsValid)
{
db.Animals.Add(animal);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(animal);
}

public ActionResult Edit(int? id)


{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRe
}
Animal animal = db.Animals.Find(id);
if (animal == null)
{
return HttpNotFound();
}
return View(animal);
}

[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);
}

public ActionResult Delete(int? id)


{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRe
}
Animal animal = db.Animals.Find(id);
if (animal == null)
{
return HttpNotFound();
}
return View(animal);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Animal animal = db.Animals.Find(id);
db.Animals.Remove(animal);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}

Ifyouwanttotestrapidlythedatabase,justaddthecodeinthe
index.
?

01
02
03
04
05
06
07
08
09
10
11
12

public ActionResult Index()


{
var animal1 = new Animal { Name = "Boss" };
var cat1 = new Cat { Name = "Mi" };
var cat2 = new Cat { Name = "Do" };
animal1.Enemies = new List<Animal> { cat1,cat2};
db.Animals.Add(animal1);
db.Animals.Add(cat1);
db.Animals.Add(cat2);
db.SaveChanges();
return View(db.Animals.AsNoTracking().ToList());
}

Thefirststepistocreatearepositoryclassforanimalinsidethe
DataAccessLayerfolder.Normally,Icreateafoldercalled
Repositorytohaveallrepositories.
?

01
02
03
04
05
06
07

public class AnimalRepository : IAnimalRepository


{
private DatabaseContext db = new DatabaseContext();
public Models.Animal Find(int? id)
{
return db.Animals.Find(id);

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

public class AnimalService: IAnimalService


{
private IAnimalRepository animalRepository;
public AnimalService(IAnimalRepository animalRepository)

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

public class AnimalController : Controller


{
private IAnimalService _service;
public AnimalController()
{
_service = new AnimalService(new AnimalRepository());
}
public AnimalController(IAnimalService animalService)
{
_service = animalService;
}
public ActionResult Index()
{
return View(_service.GetAll());
}

public ActionResult Details(int? id)


{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRe
}
Animal animal = _service.Find(id);
if (animal == null)
{
return HttpNotFound();
}
return View(animal);
}
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]

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

public ActionResult Create([Bind(Include="Id,Name")] Animal


{
if (ModelState.IsValid)
{
_service.Insert(animal);
return RedirectToAction("Index");
}
return View(animal);
}

public ActionResult Edit(int? id)


{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRe
}
Animal animal = _service.Find(id);
if (animal == null)
{
return HttpNotFound();
}
return View(animal);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="Id,Name")] Animal ani
{
if (ModelState.IsValid)
{
_service.Update(animal);
return RedirectToAction("Index");
}
return View(animal);
}

public ActionResult Delete(int? id)


{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRe

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

public interface IAnimalService


{
void Delete(Animal animal);
Animal Find(int? id);
IList<Animal> GetAll();
void Insert(Animal animal);
void Update(Animal animal);
}

Insidethecontroller,wehavetwoconstructors.Onetohelpusfor
thisexamplewhichinstantiatetheservicelayerandtherealonethat
takesasingleparameter.Thisistheonethatyoushouldhaveinyour
enterprisegradesoftwarebecauseitcaninjectanythingof
IAnimalServiceintothecontroller.
?

1
2
3
4
5
6
7
8
9

public class AnimalController : Controller


{
private IAnimalService _service;
public AnimalController(IAnimalService animalService)
{
_service = animalService;
}
//...

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

public interface IAnimalService


{
void Delete(Animal animal);
void Delete(IList<Animal> animals);
Animal Find(int? id);
IList<Animal> GetAll();
void Save(Animal animal);
void Save(IList<Animal> animal);
}
public class AnimalService: IAnimalService
{
private IAnimalRepository animalRepository;
public AnimalService(IAnimalRepository animalRepository)
{
this.animalRepository = animalRepository;
}
public Models.Animal Find(int? id)
{
return this.animalRepository.Find(id);
}
public void Delete(IList<Animal> animals)
{
foreach (var animal in animals)
{
this.animalRepository.Delete(animal);
}
this.animalRepository.Save();
}
public void Delete(Models.Animal animal)
{
this.Delete(new List<Animal> { animal });

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

public class HumanRepository : IHumanRepository


{
}
public interface IHumanRepository
{
void Insert(Models.Human humain);
}

Wealsoneedtomodifytheservicetohaveinitsconstructorthe
IHumanRepository.
?

01
02
03
04
05
06
07
08
09
10
11
12

public class AnimalService: IAnimalService


{
private IAnimalRepository animalRepository;
private IHumanRepository humanRepository;

public AnimalService(IAnimalRepository animalRepository, IHum


{
this.animalRepository = animalRepository;
this.humanRepository = humanRepository;
}
//...
}

Thenwecansimulatetheneedtohavesomethinginthesame
transactionbetweenanimalandhumanrepository.Thiscanbeinthe
SavemethodoftheAnimalService.Letscreateanewsavemethod
intheservicewhichtakeanAnimalandalsoanHuman.In
IAnimalServiceweadd.
?

void SaveAll(Animal animal, Human humain);

Andintheconcreteimplementationwehave:
?

1
2
3
4
5

public void SaveAll(Animal animal, Human humain)


{
this.animalRepository.Insert(animal);
this.humanRepository.Insert(humain);
}

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

public interface IUnitOfWork


{
IDbSet<T> Set<T>() where T:class;
DbEntityEntry<T> Entry<T>(T entity) where T:class;
void SaveChanges();
}

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

public class UnitOfWork<T>:IUnitOfWork where T : DbContext, n


{
public UnitOfWork()
{
DatabaseContext = new T();
}
private T DatabaseContext { get; set; }
public void SaveChanges()
{
DatabaseContext.SaveChanges();
}

public System.Data.Entity.IDbSet<T> Set<T>() where T : clas


{
return DatabaseContext.Set<T>();
}
public DbEntityEntry<T> Entry<T>(T entity) where T : class

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 class AnimalController : Controller


{
private IAnimalService _service;

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

public class AnimalService: IAnimalService


{
private IAnimalRepository animalRepository;
private IHumanRepository humanRepository;
private IUnitOfWork unitOfWork;
public AnimalService(IUnitOfWork unitOfWork, IAnimalRepositor
{
this.unitOfWork = unitOfWork;
this.animalRepository = animalRepository;
this.humanRepository = humanRepository;
}

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

public Animal Find(int? id)


{
return this.animalRepository.Find(id);
}
public void Delete(IList<Animal> animals)
{
foreach (var animal in animals)
{
this.animalRepository.Delete(animal);
}
this.unitOfWork.SaveChanges();
}
public void Delete(Models.Animal animal)
{
this.Delete(new List<Animal> { animal });
}
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);

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

public class AnimalRepository : WebsiteForUnitOfWork.DataAcces


{
private IUnitOfWork UnitOfWork { get; set; }
public AnimalRepository(IUnitOfWork unitOfWork)
{
this.UnitOfWork = unitOfWork;
}
public Models.Animal Find(int? id)
{
return UnitOfWork.Set<Animal>().Find(id);
}
public void Insert(Models.Animal animal)
{
UnitOfWork.Set<Animal>().Add(animal);
}
public void Update(Models.Animal animal)
{
UnitOfWork.Entry(animal).State = EntityState.Modified;
}
public void Delete(Models.Animal animal)
{

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.

Вам также может понравиться