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

ENTITYFRAMEWORKINAMULTI

LAYEREDAPPLICATION

In this blog I'll show you how you can use Entity Framework
in an MVC.Net website, with a multi layered architecture.
The main goals of the architecture to make unit testing and
integration testing easy, and keep a clean sepeartion of
concerns. To achieve this goal I will be using the concept of
Dependency Injection (DI) and Autofac als IoC Container /
DI framework.
1. Introduction
Let me start by introducing the layered architecture that I like to
use for my web applications:
Database to store data.
Data Access layer which contains the linq queries that are executed
against Entity Framework.
Domain Services layer, which holds the business logic and workflow
logic.
MVC.Net website which talks only to the Domain Services layer.
WCF services which talk only to the Domain Services layer.
This isn't an uncommon approach. Main advantages are clean
seperation of layers and easy reuse of domain logic by the MVC.Net
website and the WCF services (and windows services if you like).

2. Setting up the solution


2.1 Data Access Layer
Let's start with creating the data access layer. In this example we
have two entities:
public class Employee
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual double Salary { get; set; }
public virtual DateTime? JobEndDate { get; set; }
public virtual Organisation Organisation { get; set; }
}
public class Organisation
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual List<Employee> Employees { get; set; }
}

Now we can add the entity framework context. I use Code First, so
this is the context:
public class DatabaseContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Organisation > Organisations { get; set; }
}
Next we add two Data Access classess, which contain the linq
queries on top of the Entity Framework context:
public class OrganisationDa
{
public Organisation GetById(int id)
{
var ctx = new DatabaseContext ();
return ctx.Organisations.Single(it => it.Id == id);
}
}
public class EmployeeDa
{
public void Add(Employee employee)
{
var ctx = new DatabaseContext ();
ctx.Employees.Add(employee);
ctx.SaveChanges();
}
}
To summarize, this is what the DataAccess project looks like now:

We have a DatabaseContext, an Employee entity, an Employee


DataAccess class, an Organisation entity and an Organisation
DataAccess class.

2.2 Domain Services Layer


We can start to build the Domain Services Layer, and use the
DataAccess classes:
public class EmployeeService
{
public void AddEmployee(string name, int organisationId)
{
var organisation = new
OrganisationDa().GetById(organisationId);
var employee = new Employee();
employee.Name = "John";
employee.Organisation = organisation;
var employeeDa = new EmployeeDa();
employeeDa.Add(employee);
}
}
As you can see we create a new employee and connect that to an
existing organisation, Next we can use this service in an MVC.Net
project.

2.3 MVC Layer


public class HomeController : Controller
{
public ActionResult Index()
{
var employeeService = new EmployeeService();
employeeService.AddEmployee("John", 1);
return View();
}
}

3. First problem - Multiple Entity Framework


contexts
3.1 Introduction
If we would run the solution we created and the Index method of
the HomeController is executed, we will get this exception from
Entity Framework:
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker.
This exception happens because the Organisation entity is
referenced by the database context that was created in the GetById
method of the Organisation class, and then added to the second
database context along with the newly created employee in the Add
method of the EmployeeDa class.

3.2 Solution
We can solve this problem by using only one DatabaseContext. We
create this DatabaseContext in the AddEmployee method of the
EmployeeService and give it to each DataAccess class that is
needed:
public class EmployeeService
{
public void AddEmployee(string name, int organisationId)
{
var databasecontext = new DatabaseContext();
var organisationDa = new OrganisationDa(databasecontext);
var employeeDa = new EmployeeDa(databasecontext);
var organisation = organisationDa.GetById(organisationId);
var employee = new Employee();
employee.Name = "John";
employee.Organisation = organisation;
employeeDa.Add(e);
}
}
We also have to change the DataAccess classe to accept the
DatabaseContexta as constructor parameter:
public class OrganisationDa
{
private DatabaseContext _databasecontext;

public OrganisationDa(DatabaseContext databasecontext)


{
_databasecontext = databasecontext;
}
public Organisation GetById(int id)
{
return _databasecontext.Organisations.Single(it => it.Id ==
id);
}
}
public class EmployeeDa
{
private DatabaseContext _databasecontext;
public EmployeeDa(DatabaseContext databasecontext)
{
_databasecontext = databasecontext;
}
public Employee GetById(int id)
{
return _databasecontext.Employees.Single(it => it.Id == id);
}
public void Add(Employee employee)
{
_databasecontext.Employees.Add(employee);
_databasecontext.SaveChanges();
}
}
If we run the solution now, and execute the Index method of the
HomeController, the new Employee will be added succesfully to the
database.

4. Second problem - Not Unit Testable


4.1 Introduction
Our Domain Services can't be unit tested, but only integration tests
can be written, because it uses the data access layer and the
database.
Let's introduce a new method (GiveRaise) in the EmployeeService,
that we went to test:
public class EmployeeService
{
public void GiveRaise(int employeeId, double raise)
{
var databaseContext = new DatabaseContext();
var employeeDa = new EmployeeDa(databaseContext);
var employee = employeeDa.GetById(employeeId);
if (employee.JobEndDate.HasValue && employee.JobEndDate <
DateTime.Now)
{
throw new Exception("This employee doesn't work here
anymore");
}
employee.Salary = employee.Salary + raise;
databaseContext.SaveChanges();
}
}
We can write a test for the GiveRaise() method:
[TestMethod]
public void WorkingEmployeeCanGetRaise()
{
var employeeService = new EmployeeService();
// Act
employeeService.GiveRaise(1, 100);
// Assert
var employee = employeeService.GetById(1);
Assert.AreEqual(100, employee.Salary);
}
This test will only work if we have a database with an employee
with Id 1 in it. It's not uncommon for a build service to not have a

database. So this test can't be run. And even if there's a database,


we don't want to test the database, we want to test the logic inside
the Domain Service.

4.2 Solution one - In Memory Database


If we don't want to use a real database, we can use an in-memory
database. We have to change the code of the EmployeeServicea bit:
public class EmployeeService
{
DatabaseContext _databaseContext;
public EmployeeService(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public void GiveRaise(int employeeId, double raise)
{
var employeeDa = new EmployeeDa(_databaseContext);
var employee = employeeDa.GetById(employeeId);
if (employee.JobEndDate.HasValue && employee.JobEndDate <
DateTime.Now)
{
throw new Exception("This employee doesn't work here
anymore");
}
employee.Salary = employee.Salary + raise;
_databaseContext.SaveChanges();
}
}
Now we can inject a DatabaseContext into the EmployeeService We
can choose if we use a DatabaseContext that references a real
database, or use for example Effort as In-memory database. Let's
see how this works out in the Test:
[TestMethod]
public void WorkingEmployeeCanGetRaise()
{
// Arrange
DbConnection connection =
Effort.DbConnectionFactory.CreateTransient();
var databaseContext = new DatabaseContext(connection);

var workingEmployee = new Employee() { Id = 1 };


databaseContext.Employees.Add(workingEmployee);
databaseContext.SaveChanges();
var employeeService = new EmployeeService(context);
// Act
var employee = employeeService.GiveRaise(1, 100);
// Assert
Assert.AreEqual(100, employee.Salary);

4.3 Solution two - Mock objects


If we don't want to do anything with the DataAccess layer, we can
use a fake DataAccess Layer, that doesn't need a database but just
returns an employee. How does this work?
First we need to have a mocking framework, to create a mock of a
the Employee DataAccess class. I like to use Moq. So I installed the
nuget package of Moq into the Test project.
Moq, like most mocking libraries, can only create a mock from an
interface, so we need to make an interface for EmployeeDa:
public interface IEmployeeDa
{
Employee GetById(int id);
void Add(Employee employee);
}
Let EmployeeDa implement this interface:
public class EmployeeDa : IEmployeeDa
{
private DatabaseContext _databaseContext;
public EmployeeDa(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public Employee GetById(int id)
{
return _databaseContext.Employees.Single(it => it.Id == id);
}

public void Add(Employee employee)


{
_databaseContext.Employees.Add(employee);
_databaseContext.SaveChanges();
}
}
Now we can create a mock of IEmployeeDa in the test:
var employeeDaMock = new Mock<IEmployeeDa>();
employeeDaMock
.Setup<Employee>(it => it.GetById(1))
.Returns(new Employee() { Id = 1, Salary = 0 });
var employeeDa = employeeDaMock.Object;
So how do we use this mocked EmployeeDa in the
EmployeeService We can inject it in the constructor, assign it to a
private field, and use it in the methods like this:
public class EmployeeService
{
DatabaseContext _databaseContext;
IEmployeeDa _employeeDa;
public EmployeeService(DatabaseContext databaseContext,
IEmployeeDa employeeDa)
{
_databaseContext = databaseContext;
_employeeDa = employeeDa;
}
public Employee GiveRaise(int employeeId, double raise)
{
var employee = _employeeDa.GetById(employeeId);
if (employee.JobEndDate.HasValue && employee.JobEndDate <
DateTime.Now)
{
throw new Exception("This employee doesn't work here
anymore");
}
employee.Salary = employee.Salary + raise;
_databaseContext.SaveChanges();

return employee;
}
}
To conclude this chapter, this is the complete test:
[TestMethod]
public void WorkingEmployeeCanGetRaise()
{
// Arrange
var context = new DatabaseContext();
var employeeDaMock = new Mock<IEmployeeDa>();
employeeDaMock
.Setup<Employee>(it => it.GetById(1))
.Returns(new Employee() { Id = 1, Salary = 0 });
var employeeDa = employeeDaMock.Object;
var employeeService = new EmployeeService(context,
employeeDa);
// Act
var employee = employeeService.GiveRaise(1, 100);
// Assert
Assert.AreEqual(100, employee.Salary);

5. Third problem - Unwanted EF queries in


Domain Services
5.1 Introduction
Since the Domain Services have access to the Entity Framework
context, it's possible to write queries to directly load entities from
the database. This goes against the Single Responsibility Principle
and Seperation of Concerns.
It's possible to do this in the EmployeeService:
public Employee GiveRaise(int employeeId, double raise)
{
var employee = _databaseContext.Employees
.Include("Organisation")
.Single(it=>it.Id = employeeId);
...
}
What's wrong about this? The main problem is that once we start
this way, soon a lot of methods in the Domain Services Layer will
have queries on top of Entity Framework.
How can we prevent this?
}

5.2 Solution - Only expose Unit of Work


The solution is quite simple: the Domain Services Layer only needs
the Unit of Work from Entity Framework, and not the Repositories.
In other words, only the method SaveChanges() is needed, and not
all the DbSets.
We can create a wrapper for the Entity Framework context like this:
public class UnitOfWork
{
DatabaseContext _databaseContext;
public UnitOfWork(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public int SaveChanges()
{
return _databaseContext.SaveChanges();

}
}
Use the UnitOfWork in the Domain Services Layer this way:
public class EmployeeService : IEmployeeService
{
UnitOfWork _unitOfWork;
IEmployeeDa _employeeDa;
IOrganisationDa _organisationDa;
public EmployeeService(UnitOfWork unitOfWork,
IEmployeeDa employeeDa,
IOrganisationDa organisationDa)
{
_unitOfWork = unitOfWork;
_employeeDa = employeeDa;
_organisationDa = organisationDa;
}
public Employee GiveRaise(int employeeId, double raise)
{
var employee = _employeeDa.GetById(employeeId);
if (employee.JobEndDate.HasValue && employee.JobEndDate <
DateTime.Now)
{
throw new Exception("This employee doesn't work here
anymore");
}
employee.Salary = employee.Salary + raise;
_unitOfWork.SaveChanges();
return employee;
}
}

5.3 Conclusion
The Entity Framework context is not exposed to the Domain
Services Layer anymore, but only the Unit of Work is accessible.
This means the we can no longer write direct queries onto Entity
Framework from the Domain Services Layer, but the have the be
written in the Data Access Layer. This leads to cleaner code and
satisfies the Seperation of Concern better.

6 Fourth problem - Dirty Web Layer


6.1 Introduction

When we go back to the the MVC.Net project / Web Layer, we have


to write this code to give an employee a raise:
public class HomeController : Controller
{
public ActionResult Index()
{
var databaseContext = new DatabaseContext();
var unitOfWork = new UnitOfWork(databaseContext);
var employeeDa = new EmployeeDa(databaseContext);
var organisationDa = new OrganisationDa(databaseContext);
var employeeService = new EmployeeService(unitOfWork,
employeeDa,
organisationDa);
employeeService.GiveRaise(1, 100);
return View();
}
}
Now we have problems two and three congregated in one project.
We can't unit test the HomeController, we have access to the
DatabaseContext and can write direct queries on top of Entity
Framework.
Fortunately we can solve this. Unfortunately the solution to this
problem takes a quite a bit of work. Let's start with part one of the
solution.

6.2 Solution one - Move initialisation to the


constructor
public class HomeController : Controller
{
private EmployeeService _employeeService;
public HomeController() : this(new DatabaseContext())
{
}
public HomeController(DatabaseContext databaseContext)
{
var unitOfWork = new UnitOfWork(databaseContext);
var employeeDa = new EmployeeDa(databaseContext);
var organisationDa = new OrganisationDa(databaseContext);
_employeeService = new EmployeeService(unitOfWork,
employeeDa,

organisationDa);
}
public ActionResult Index()
{
_employeeService.GiveRaise(1, 100);
return View();
}
}
There are two things nice about the code above:
1. The index() method doesn't have to worry about initialising the
EmployeeService anymore;
2. If we want to test the Index() method, we can inject the
DatabaseContext, so we can use for example an in memory
database.
But there are also a few things that aren't so great:
1. We have access to the DatabaseContext and can write direct
queries on top of Entity Framework;
2. We can't mock the EmployeeService;
3. If the EmployeeService needs another DataAccess class, we
have to add it in the constructor of the controller too. The more
controllers there are, the more work this is. This quickly gets pretty
tedious.
Therefore, let's look at part two of the solution

6.3 Solution two - Use an IoC container


IoC containers are used to give control over instantiating object to
an external framework. You set them up once, and the create and
inject the object everytime it is needed. In the HomeController we
need and EmployeeService, and we create it ourselves in the
constructor. But we can use and IoC container for that too. My
favorite one is Autofac.
After we installed the NugetPackage for Autofac (Autofac and
Autofac ASP.NET MVC4 Integration) into the MVC project, we can
setup autofac in the global.asax:
// needed usings:
// using Autofac;
// using Autofac.Integration.Mvc;
protected void Application_Start()
{
...

var builder = new Autofac.ContainerBuilder();


builder.RegisterControllers(typeof(MvcApplication).Assembly).Proper
tiesAutowired();
// [Insert custom initialisation here]
var container = builder.Build();
DependencyResolver.SetResolver(new
AutofacDependencyResolver(container));
}
As Autofac is setup, we can start to register our Services and
DataAccess classes. I like to do that in a new project, to keep the
Web layer clean of all references that are not neccessary. I name
this project AutofacInitialiser.
First we register the DataAcces classes
namespace Xample.AutofacInitialiser
{
public class DataAccessModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("Xmpl.DataAccess"))
.Where(t => t.Name.EndsWith("Da"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
}
And register this module in the global.asax:
protected void Application_Start()
{
...
var builder = new Autofac.ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly).Proper

tiesAutowired();
builder.RegisterModule(new DataAccessModule());
var container = builder.Build();
DependencyResolver.SetResolver(new
AutofacDependencyResolver(container));
}
What did we just do? We've created a Module that scans the
DataAccess Layer and registers every class the ends with Da in
Autofac. This means that if we write OrderDa, or ProductDa, or
whateverDa, they are registered automatically with autofac, without
writing any special code.
We can write a Module for the Domain Services too:
public class DomainServicesModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("Xmpl.DomainServic
es"))
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
and register it in the global.asax:
protected void Application_Start()
{
...

var builder = new Autofac.ContainerBuilder();


builder.RegisterControllers(typeof(MvcApplication).Assembly).Proper
tiesAutowired();
builder.RegisterModule(new DataAccessModule());
builder.RegisterModule(new DomainServicesModule());
var container = builder.Build();
DependencyResolver.SetResolver(new
AutofacDependencyResolver(container));
}
And finally we write a module for all Entity Framework related
classes:
public class EntityFrameworkModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterModule(new DataAccessModule());
builder.RegisterType<DatabaseContext>()
.AsSelf()
.InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork>()
.AsSelf()
.InstancePerLifetimeScope();
}
}
and register it in the global.asax:

protected void Application_Start()


{
...
var builder = new Autofac.ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly).Proper
tiesAutowired();
builder.RegisterModule(new DataAccessModule());
builder.RegisterModule(new DomainServiceModule());
builder.RegisterModule(new EntityFrameworkModule());
var container = builder.Build();
DependencyResolver.SetResolver(new
AutofacDependencyResolver(container));
}
Now we can inject the EmployeeService into the HomeController like
this:
public class HomeController : Controller
{
private IEmployeeService _employeeService;
public HomeController(IEmployeeService employeeService)
{
_employeeService = employeeService;
}
...
}

6.4 Conclusion
As you can see, there's no need to instansiate the Data Access
classes, or anything related to Entity Framework anymore. This
means we can even remove the reference to Entity Framework from
the Web Layer. We have achieved a clean solution which solves the
problems we had before:

1. We have no access to the DatabaseContext from the Web Layer,


and can't write direct queries on top of Entity Framework;
2. We can mock the EmployeeService;
3. If the EmployeeService needs another DataAccess class, we
don't have to add it in the constructor of the controller anymore,
and don't have to add anything to the Autofac inisialisation code.

7 Final thoughts
In this blog I wrote how to create a Visual Studio solution with three
layers (Data Access, Domain Services and Web) which are clearly
seperated from each other, and are easy to test. I've achieved this
by wrapping the Entity Framework context and by using Autofac as
IoC container.

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