donderdag 28 februari 2013

Entity Framework in a multi layered application

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 { getset; }
   public virtual double Salary { getset; }
   public virtual DateTime? JobEndDate { getset; }
   public virtual Organisation Organisation { getset; }
}

public class Organisation

{
   public virtual int Id { getset; }
   public virtual string Name { getset; }
   public virtual List<Employee> Employees { getset; }
}


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 { getset; }
   public DbSet<Organisation > Organisations { getset; }
}


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).PropertiesAutowired();

   // [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).PropertiesAutowired();

   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.DomainServices"))
                .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).PropertiesAutowired();


   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).PropertiesAutowired();


   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.

1 opmerking:

  1. Hi do you have the source code of the final solution? It is a little bit hard to follow all the steps... cheers

    BeantwoordenVerwijderen