Академический Документы
Профессиональный Документы
Культура Документы
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).
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:
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;
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);
}
}
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.
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
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()
{
...
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:
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.