My .NET focused coding blog.

Implementing a generic data access layer using Entity Framework

This post is about how you can develop a generic data access layer (DAL) with full CRUD (Create, Read, Update and Delete) support using Entity Framework 5 with plain old CLR objects (POCOs) and short-lived contexts in a disconnected and stateless N-tier application.

Entity Framework (EF) is Microsoft’s recommended data access technology when building new .NET applications. It is an object-relational mapping framework (ORM) that enables developers to work with relational data using domain-specific objects without having to write code to access data from a database.

EF provides three options for creating the conceptual model of your domain entities, also known as the entity data model (EDM); database first, model first and code first. With both the model first and code first approaches the presumption is that you don’t have an existing database when you start developing the application and a database schema is created based on the model. As databases within enterprise environments are generally designed and maintained by database administrators (DBAs) rather than developers, this post will use the database first option where the EDM becomes a virtual reflection of a database or a subset of it.

Typically when you are doing database first development using EF you are targeting an already existing database but for testing and demo purposes you may of course generate a new one from scratch. There is a walkthrough on how you can create a local service-based database in Visual Studio 2012 available on MSDN here.

The database used in this example is a very simple one containing only the two tables listed below. There is a one-to-many relationship between the Department and Employee tables meaning an employee belongs to a single department and a department can have several employees.

CREATE TABLE [dbo].[Department] (
    [DepartmentId] INT          IDENTITY (1, 1) NOT NULL,
    [Name]         VARCHAR (50) NULL,
    PRIMARY KEY CLUSTERED ([DepartmentId] ASC)
);

CREATE TABLE [dbo].[Employee] (
    [EmployeeId]   INT          IDENTITY (1, 1) NOT NULL,
    [DepartmentId] INT          NOT NULL,
    [FirstName]    VARCHAR (20) NOT NULL,
    [LastName]     VARCHAR (20) NOT NULL,
    [Email]        VARCHAR (50) NULL,
    PRIMARY KEY CLUSTERED ([EmployeeId] ASC),
    CONSTRAINT [FK_Employee_Department] FOREIGN KEY ([DepartmentId])
    REFERENCES [dbo].[Department] ([DepartmentId])
);

N-tier architecture

A large enterprise application will typically have one or more databases to store data and on top of this a data access layer (DAL) to access the database(s). On top of this there may be some repositories to communicate with the DAL, a business layer containing logic and classes representing the business domain, a service layer to expose the business layer to clients and finally some user interface application such as a WPF desktop application or an ASP.NET web application.

User interface layer
WPF / ASP.NET / Console App / WinRT / …
Service layer
WCF / ASMX / …
Business logic layer
Data access layer
EF / ADO.NET / …
Database
SQL Server / Oracle / MySql / …

Data access layer (DAL)

The DAL is simply a C# class library project where you define the model generated from the existing database along with the generic implementation for reading and modifying the data in the database. It is the only layer in the application that will actually know anything about and have any dependencies on EF. Any user interface code should only communicate with the service or business layer and don’t have any references to the DAL.

1. Start by creating a new class library project (Mm.DataAccessLayer) and add a new ADO.NET Entity Data Model to it. Choose the “Generate from database” option in the Entity Data Model wizard. The wizard lets you connect to the database and select the Department and Employee tables to be included in the model.

wizard2 Entity Data Model

Once the wizard has completed the model is added to your project and you are able to view it in the EF Designer. By default all generated code including the model classes for the Department and Employee entities sits in the same project.

Separating entity classes from EDMX

Again, in an enterprise level application where separation of concerns is of great importance you certainly want to have your domain logic and your data access logic in separate projects. In other words you want to move the generated model (Model.tt) to another project. This can easily be accomplished by following these steps:

2. Add a new class library project (Mm.DomainModel) to the solution in Visual Studio.
3. Open File Explorer (right-click on the solution in Visual Studio and choose the “Open Folder in File Explorer” option) and move the Model.tt file to the new project folder.
4. Back in Visual Studio, include the Model.tt file in the new project by clicking on the “Show All Files” icon at the top of the Solution Explorer and then right-click on the Model.tt file and choose the “Include In Project” option.
5. Delete the Model.tt file from the DAL project.
6. For the template in the new domain model project to be able to find the model you then need to modify it to point to the correct EDMX path. You do this by setting the inputFile variable in the Model.tt template file to point to an explicit path where to find the model:

const string inputFile = @"../Mm.DataAccessLayer/Model.edmx";

Once you save the file the entity classes should be generated in the domain model project. Note that if you make any changes to the model in the DAL project later on you are required to explicitly update your model classes. By right-click on the Model.tt template file and choose “Run Custom Tool” the entity classes will be regenerated to reflect the latest changes to the model.

7. As the context by default expects the entity classes to be in the same namespace, add a using statement for their new namespace to the Model.Context.tt template file in the DAL project:

using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using Mm.DomainModel; <!-- Added -->
<#
if (container.FunctionImports.Any())
{
#>
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;
<#
}
#>

8. Finally, you need to add a reference from the DAL project to the domain model project in order for it to compile.

DbContext

In an EF-based application a context is responsible for tracking changes that are made to the entities after they have been loaded from the database. You then use the SaveChanges method on the context to persist the changes back to the database.

By default EDMs created in Visual Studio 2012 generates simple POCO entity classes and a context that derives from DbContext and this is the recommended template unless you have a reason to use one of the others listed on MSDN here.

The DbContext class was introduced in EF 4.1 and provides a simpler and more lightweight API compared to the EF 4.0 ObjectContext. However it simply acts like a wrapper around the ObjectContext and if you for some reason need the granular control of the latter you can implement an extension method – extension methods enable you to “add” methods to existing types without creating a new derived type – to be able to convert the DbContext to an ObjectContext through an adapter:

using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;

namespace Mm.DataAccessLayer
{
    public static class DbContextExtensions
    {
        public static ObjectContext ToObjectContext(this DbContext dbContext)
        {
            return (dbContext as IObjectContextAdapter).ObjectContext;
        }
    }
}

Encapsulating data access into repositories

A repository is responsible for encapsulating the data access code. It sits between the DAL and the business layer of the application to query the data source for data and map this data to an entity class, and it also persists changes in the entity classes back to the data source using the context.

A repository typically implements an interface that provides a simple set of methods for the developer using the repository to code against. Using an interface the consumer doesn’t need to know anything about what happens behind the scenes, i.e. whether the DAL uses EF, another ORM or manually creating connections and commands to execute queries against a data source. Besides the abstraction it brings it’s also great if you are using dependency injection in your application.

By using a generic repository for querying and persisting changes for your entity classes you can maximize code reuse. Below is a sample generic interface which provides methods to query for all entities, specific entities matching a given where predicate and a single entity as well as methods for inserting, updating and removing an arbitrary number of entities.

9. Add the below interface named IGenericDataRepository to the Mm.DataAccessLayer project.

using Mm.DomainModel;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Mm.DataAccessLayer
{
    public interface IGenericDataRepository<T> where T : class
    {
        IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties);
        IList<T> GetList(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
        T GetSingle(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
        void Add(params T[] items);
        void Update(params T[] items);
        void Remove(params T[] items);
    }
}

IList vs IQueryable

Note that the return type of the two Get* methods is IList<T> rather than IQueryable<T>. This means that the methods will be returning the actual already executed results from the queries rather than executable queries themselves. Creating queries and return these back to the calling code would make the caller responsible for executing the LINQ-to-Entities queries and consequently use EF logic. Besides, when using EF in an N-tier application the repository typically creates a new context and dispose it on every request meaning the calling code won’t have access to it and therefore the ability to cause the query to be executed. Thus you should always keep your LINQ queries inside of the repository when using EF in a disconnected scenario such as in an N-tier application.

Loading related entities

EF offers two categories for loading entities that are related to your target entity, e.g. getting employees associated with a department in this case. Eager loading uses the Include method on the DbSet to load child entities and will issue a single query that fetches the data for all the included entities in a single call. Each of the methods for reading data from the database in the concrete sample implementation of the IGenericDataRepository<T> interface below supports eager loading by accepting a variable number of navigation properties to be included in the query as arguments.

10. Add a new class named GenericDataRepository to the MM.DataAccessLayer project and implement the IGenericDataRepository<T> interface .

using Mm.DomainModel;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;

namespace Mm.DataAccessLayer
{
    public class GenericDataRepository<T> : IGenericDataRepository<T> where T : class
    {
        public virtual IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties)
        {
            List<T> list;
            using (var context = new Entities())
            {
                IQueryable<T> dbQuery = context.Set<T>();

                //Apply eager loading
                foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                    dbQuery = dbQuery.Include<T, object>(navigationProperty);
                
                list = dbQuery
                    .AsNoTracking()
                    .ToList<T>();
            }
            return list;
        }

        public virtual IList<T> GetList(Func<T, bool> where, 
             params Expression<Func<T,object>>[] navigationProperties)
        {
            List<T> list;
            using (var context = new Entities())
            {
                IQueryable<T> dbQuery = context.Set<T>();
                
                //Apply eager loading
                foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                    dbQuery = dbQuery.Include<T, object>(navigationProperty);

                list = dbQuery
                    .AsNoTracking()
                    .Where(where)
                    .ToList<T>();
            }
            return list;
        }

        public virtual T GetSingle(Func<T, bool> where,
             params Expression<Func<T, object>>[] navigationProperties)
        {
            T item = null;
            using (var context = new Entities())
            {
                IQueryable<T> dbQuery = context.Set<T>();
                
                //Apply eager loading
                foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
                    dbQuery = dbQuery.Include<T, object>(navigationProperty);

                item = dbQuery
                    .AsNoTracking() //Don't track any changes for the selected item
                    .FirstOrDefault(where); //Apply where clause
            }
            return item;
        }
        
        /* rest of code omitted */
    }
}

For example, here’s how you would call the GetAll method to get all departments with its employees included:

IGenericDataRepository<Department> repository = new GenericDataRepository<Department>();
IList<Department> departments = repository.GetAll(d => d.Employees);

With lazy loading related entities are loaded from the data source by EF issuing a separate query first when the get accessor of a navigation property is accessed programmatically.

Dynamic proxies

For EF to enable features such as lazy loading and automatic change tracking for POCO entities, it can create a wrapper class around the POCO entity at runtime. There is a certain set of rules that your entity classes need to follow to get this proxy behavior. To get the instant change tracking behavior every property must be marked as virtual. For the lazy loading to work, those related properties that you want to be lazily loaded must be marked as virtual and those who point to a set of related child objects have to be of type ICollection. There is a complete list of the requirements for POCO proxies to be created available on MSDN here if you want more information.

Disconnected entities

However, in an N-tier application entity objects are usually disconnected meaning they are not being tracked by a context as the data is fetched using one context, returned to the client where there is no context to track changes and then sent back to the server and persisted back to the database using another instance of the context. Looking at the code above, a new instance of the context will be created and disposed for each method call and the AsNoTracking extension method – also added in EF 4.1 – is used to tell the context not to track any changes which may result in better performance when querying for a large number of entities. When using short-lived contexts like this, you should disable lazy loading. If you don’t an exception saying the context has been disposed will be thrown whenever a non-initialized navigation property is accessed from anywhere outside the context’s scope.

11. Lazy loading and dynamic proxy creation is turned off for all entities in a context by setting two flags on the Configuration property on the DbContext as shown below. Both these properties are set to true by default.

namespace Mm.DataAccessLayer
{
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using Mm.DomainModel;
    
    public partial class Entities : DbContext
    {
        public Entities()
            : base("name=Entities")
        {
            Configuration.LazyLoadingEnabled = false;
            Configuration.ProxyCreationEnabled = false;
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    
        public DbSet<Department> Departments { get; set; }
        public DbSet<Employee> Employees { get; set; }
    }
}

Root vs Graphs

When it comes to persisting changes to the database you need to decide whether your CUD methods should accept an entire graph of entities or only a single root entity to be passed in. A graph of entities is a number of entities that reference each other. For example, when you want to insert a new Department entity to the database by passing it to the repository’s Add method it might have related Employee objects. In this case the Employee objects belong to the graph and the Department object is the root entity.

EntityState

On the server side, things will get easier if you decide to not support graphs. In this case you could expose an Add method and an Update method for each entity type and these methods would only operate on a standalone instance rather than a graph of entities. EF makes it simple to implement these methods. It is all about setting the state of the passed in entity object. An entity can be in one of five states as defined by the System.Data.EntityState enumeration:

Added: the entity is being tracked by the context but hasn’t been added to the database yet.

Unchanged: the entity is being tracked by the context, it exists in the database but its property values have not been changed since it was fetched from the database.

Modified: the entity is being tracked by the context, it exists in the database and some or all of its property values have been modified since it was fetched from the database

Deleted: the entity is being tracked by the context, it exists in the database but will be deleted on the next call to the SaveChanges method.

Detached: the entity is not being tracked by the context at all.

When the context’s SaveChanges method is called it decides what to do based on the entity’s current state. Unchanged and detached entities are ignored while added entities are inserted into the database and then become Unchanged when the method returns, modified entities are updated in the database and then become Unchanged and deleted entities are deleted from the database and then detached from the context.

DbSet.Entry

You can explicitly change the state of an entity by using the DbSet.Entry method. There is no need to attach the entity to the context before using this method as it will automatically do the attachment if needed. Below is the implementation of the generic repository’s Add method. It explicitly sets the state of the entity to be inserted into the database to Added before calling SaveChanges to execute and commit the insert statement.

public virtual void Add(params T[] items)
{
    using (var context = new Entities())
    {
        foreach (T item in items)
        {
            context.Entry(item).State = System.Data.EntityState.Added;
        }
        context.SaveChanges();
    }
}

The implementation for the Update and Remove methods are very similar to the Add method as shown below. Note that all exception handling has been omitted for brevity in the sample code.

public virtual void Update(params T[] items)
{
    using (var context = new Entities())
    {
        foreach (T item in items)
        {
            context.Entry(item).State = System.Data.EntityState.Modified;
        }
        context.SaveChanges();
    }
}

public virtual void Remove(params T[] items)
{
    using (var context = new Entities())
    {
        foreach (T item in items)
        {
            context.Entry(item).State = System.Data.EntityState.Deleted;
        }
        context.SaveChanges();
    }
}

Also note that all methods have been marked as virtual. This allows you to override any method in the generic repository by adding a derived class in cases where you need some specific logic to apply only to a certain type of entity. To be able to extend the generic implementation with methods that are specific only to a certain type of entity, whether it’s an initial requirement or a possible future one, it’s considered a good practice to define a repository per entity type from the beginning. You can simply inherit these repositories from the generic one as shown below and add methods to extend the common functionality based on your needs.

12. Add interfaces and classes to represent specific repositories for the Department and Employee entities to the DAL project.

using Mm.DomainModel;

namespace Mm.DataAccessLayer
{
    public interface IDepartmentRepository : IGenericDataRepository<Department>
    {
    }

    public interface IEmployeeRepository : IGenericDataRepository<Employee>
    {
    }

    public class DepartmentRepository : GenericDataRepository<Department>, IDepartmentRepository
    {
    }

    public class EmployeeRepository : GenericDataRepository<Employee>, IEmployeeRepository
    {
    }
}

Business layer

As mentioned before, the repository is located somewhere between the DAL and the business layer in a typical N-tier architecture. The business layer will use it to communicate with the database through the EDM in the DAL. Any client application will be happily unaware of any details regarding how data is fetched or persisted on the server side. It’s the responsibility of the business layer to provide methods for the client to use to communicate with the server.

13. Add a new project (Mm.BusinessLayer) to the solution with references to the DAL project (Mm.DataAccessLayer) and the project with the domain classes (Mm.DomainModel). Then add a new interface and a class implementing this interface to it to expose methods for creating, reading, updating and deleting entities to any client application.

Below is a sample implementation. In a real world application the methods in the business layer would probably contain code to validate the entities before processing them and it would also be catching and logging exceptions and maybe do some caching of frequently used data as well.

using Mm.DomainModel;
using System.Collections.Generic;
using Mm.DataAccessLayer;

namespace Mm.BusinessLayer
{
    public interface IBusinessLayer
    {
        IList<Department> GetAllDepartments();
        Department GetDepartmentByName(string departmentName);
        void AddDepartment(params Department[] departments);
        void UpdateDepartment(params Department[] departments);
        void RemoveDepartment(params Department[] departments);

        IList<Employee> GetEmployeesByDepartmentName(string departmentName);
        void AddEmployee(Employee employee);
        void UpdateEmploee(Employee employee);
        void RemoveEmployee(Employee employee);
    }

    public class BuinessLayer : IBusinessLayer
    {
        private readonly IDepartmentRepository _deptRepository;
        private readonly IEmployeeRepository _employeeRepository;

        public BuinessLayer()
        {
            _deptRepository = new DepartmentRepository();
            _employeeRepository = new EmployeeRepository();
        }

        public BuinessLayer(IDepartmentRepository deptRepository,
            IEmployeeRepository employeeRepository)
        {
            _deptRepository = deptRepository;
            _employeeRepository = employeeRepository;
        }

        public IList<Department> GetAllDepartments()
        {
            return _deptRepository.GetAll();
        }

        public Department GetDepartmentByName(string departmentName)
        {
            return _deptRepository.GetSingle(
                d => d.Name.Equals(departmentName), 
                d => d.Employees); //include related employees
        }

        public void AddDepartment(params Department[] departments)
        {
            /* Validation and error handling omitted */
            _deptRepository.Add(departments);
        }

        public void UpdateDepartment(params Department[] departments)
        {
            /* Validation and error handling omitted */
            _deptRepository.Update(departments);
        }

        public void RemoveDepartment(params Department[] departments)
        {
            /* Validation and error handling omitted */
            _deptRepository.Remove(departments);
        }

        public IList<Employee> GetEmployeesByDepartmentName(string departmentName)
        {
            return _employeeRepository.GetList(e => e.Department.Name.Equals(departmentName));
        }

        public void AddEmployee(Employee employee)
        {
            /* Validation and error handling omitted */
            _employeeRepository.Add(employee);
        }

        public void UpdateEmploee(Employee employee)
        {
            /* Validation and error handling omitted */
            _employeeRepository.Update(employee);
        }

        public void RemoveEmployee(Employee employee)
        {
            /* Validation and error handling omitted */
            _employeeRepository.Remove(employee);
        }
    }
}

Client

A client application consuming the sever side code will only need references to the business layer and the entity classes defined in the Mm.DomainModel project. Below is a simple C# console application to test the functionality provided by the business layer. It’s important to note that there are no references or dependencies to EF in this application. In fact you could replace the EF-based DAL with another one using raw T-SQL commands to communicate with the database without affecting the client side code. The only thing in the console application that hints that EF may be involved is the connection string that was generated in the DAL project when the EDM was created and has to be added to the application’s configuration file (App.config). Connection strings used by EF contain information about the required model, the mapping files between the model and the database and how to connect to the database using the underlying data provider.

14. To be able to test the functionality of the business layer and the DAL, create a new console application and add references to the Mm.BusinessLayer project and the Mm.DomainModel project.

using Mm.BusinessLayer;
using Mm.DomainModel;
using System;
using System.Collections.Generic;

namespace Mm.ConsoleClientApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            IBusinessLayer businessLayer = new BuinessLayer();
            
            /* Create some departments and insert them to the database through the business layer */
            Department it = new Department() { Name = "IT" };
            Department sales = new Department() { Name = "Sales" };
            Department marketing = new Department() { Name = "Marketing" };
            businessLayer.AddDepartment(it, sales, marketing);

            /* Get a list of departments from the database through the business layer */
            Console.WriteLine("Existing departments:");
            IList<Department> departments = businessLayer.GetAllDepartments();
            foreach (Department department in departments)
                Console.WriteLine(string.Format("{0} - {1}", department.DepartmentId, department.Name));

            
            /* Add a new employee and assign it to a department */
            Employee employee = new Employee()
            {
                FirstName = "Magnus",
                LastName = "Montin",
                DepartmentId = it.DepartmentId
            };
            businessLayer.AddEmployee(employee);
            
            /* Get a single department by name */
            it = businessLayer.GetDepartmentByName("IT");
            if (it != null)
            {
                Console.WriteLine(string.Format("Employees at the {0} department:", it.Name));
                foreach (Employee e in it.Employees)
                    Console.WriteLine(string.Format("{0}, {1}", e.LastName, e.FirstName));
            };

            /* Update an existing department */
            it.Name = "IT Department";
            businessLayer.UpdateDepartment(it);

            /* Remove employee */
            it.Employees.Clear();
            businessLayer.RemoveEmployee(employee);
            
            /* Remove departments*/
            businessLayer.RemoveDepartment(it, sales, marketing);

            Console.ReadLine();
        }
    }
}

Output

Persisting disconnected graphs

While avoiding the complexity of accepting graphs of objects to be persisted at once makes life easier for server side developers, it potentially makes the client component more complex. As you may have noticed by looking at the code for the business layer above, you are also likely to end up with a large number of operations exposed from the server. If you do want your business layer to be able to handle graphs of objects to passed in and be persisted correctly, you need a way of determining what changes were made to the passed in entity objects in order for you to set their states correctly.

For example, consider a scenario when you get a Department object representing a graph with related Employee objects. If all entities in the graph are new, i.e. are not yet in the database, you can simply call the DbSet.Add method to set the state of all entities in the graph to Added and call the SaveChanges to persist the changes. If the root entity, the Department in this case, is new and all related Employee objects are unchanged and already existing in the database you can use the DbSet.Entry method to change the state of the root only. If the root entity is modified and some related items have also been changed, you would first use the DbSet.Entry method to set the state of the root entity to Modified. This will attach the entire graph to the context and set the state of the related objects to Unchanged. You will then need to identify the related entities that have been changed and set the state of these to Modified too. Finally, you may have a graph with entities of varying states including added ones. The best thing here is to use the DbSet.Add method to set the states of the related entities that were truly added to Added and then use the DbSet.Entry method to set the correct state of the other ones.

So how do you know the state of an entity when it comes from a disconnected source and how do you make your business layer able to persist a graph with a variety of objects with a variety of states? The key here is to have the entity objects track their own state by explicitly setting the state on the client side before passing them to the business layer. This can be accomplished by letting all entity classes implement an interface with a state property. Below is a sample interface and an enum defining the possible states.

namespace Mm.DomainModel
{
    public interface IEntity
    {
        EntityState EntityState { get; set; }
    }

    public enum EntityState
    {
        Unchanged,
        Added,
        Modified,
        Deleted
    }
}
/* Entity classes implementing IEntity */
public partial class Department : IEntity
{
    public Department()
    {
        this.Employees = new HashSet<Employee>();
    }
    
    public int DepartmentId { get; set; }
    public string Name { get; set; }
    
    public virtual ICollection<Employee> Employees { get; set; }

    public EntityState EntityState { get; set; }
}

public partial class Employee : IEntity
{
    public int EmployeeId { get; set; }
    public int DepartmentId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    
    public virtual Department Department { get; set; }

    public EntityState EntityState { get; set; }
}

With this solution, the business layer will know the state of each entity in a passed in graph assuming the states have been set correctly in the client application. The repository will need a helper method to convert the custom EntityState value to a System.Data.EntityState enumeration value. The below static method can be added to the GenericDataRepository<T> class in the DAL to takes care of this.

protected static System.Data.EntityState GetEntityState(Mm.DomainModel.EntityState entityState)
{
    switch (entityState)
    {
        case DomainModel.EntityState.Unchanged:
            return System.Data.EntityState.Unchanged;
        case DomainModel.EntityState.Added:
            return System.Data.EntityState.Added;
        case DomainModel.EntityState.Modified:
            return System.Data.EntityState.Modified;
        case DomainModel.EntityState.Deleted:
            return System.Data.EntityState.Deleted;
        default:
            return System.Data.EntityState.Detached;
    }
}

Next, you need to specify a constraint on the IGenericDataRepository<T> interface and the GenericDataRepository<T> class to ensure that the type parameter T implements the IEntity interface and then make some modifications to the CUD methods in the repository as per below. Note that the Update method will actually be able to do all the work now as it basically only sets the System.Data.EntityState of an entity based on the value of the custom enum property.

public interface IGenericDataRepository<T> where T : class, IEntity { ... }
public virtual void Add(params T[] items)
{
    Update(items);
}

public virtual void Update(params T[] items)
{
    using (var context = new Entities())
    {
        DbSet<T> dbSet = context.Set<T>();
        foreach (T item in items)
        {
            dbSet.Add(item);
            foreach (DbEntityEntry<IEntity> entry in context.ChangeTracker.Entries<IEntity>())
            {
                IEntity entity = entry.Entity;
                entry.State = GetEntityState(entity.EntityState);
            }
        }
        context.SaveChanges();
    }
}

public virtual void Remove(params T[] items)
{
    Update(items);
}

Also note they key to all this working is that the client application must set the correct state of an entity as the repository will be totally dependent on this. Finally, below is some client side code that shows how to set the state of entities and passing a graph of objects to the business layer.

using Mm.BusinessLayer;
using Mm.DomainModel;
using System;
using System.Collections.Generic;

namespace Mm.ConsoleClientApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            IBusinessLayer businessLayer = new BuinessLayer();
            
            /* Create a department graph with two related employee objects */
            Department it = new Department() { Name = "IT" };
            it.Employees = new List<Employee> 
            { 
                new Employee { FirstName="Donald", LastName="Duck", EntityState=EntityState.Added },
                new Employee { FirstName="Mickey", LastName="Mouse", EntityState=EntityState.Added }
            };
            it.EntityState = EntityState.Added;
            businessLayer.AddDepartment(it);

            /*Add another employee to the IT department */
            Employee employee = new Employee()
            {
                FirstName = "Robin",
                LastName = "Hood",
                DepartmentId = it.DepartmentId,
                EntityState = EntityState.Added
            };
            /* and change the name of the department */
            it.Name = "Information Technology Department";
            it.EntityState = EntityState.Modified;
            foreach (Employee emp in it.Employees)
                emp.EntityState = EntityState.Unchanged;
            it.Employees.Add(employee);
            businessLayer.UpdateDepartment(it);

            /* Verify changes by quering for the updated department */
            it = businessLayer.GetDepartmentByName("Information Technology Department");
            if (it != null)
            {
                Console.WriteLine(string.Format("Employees at the {0} department:", it.Name));
                foreach (Employee e in it.Employees)
                    Console.WriteLine(string.Format("{0}, {1}", e.LastName, e.FirstName));
            };

            /* Delete all entities */
            it.EntityState = EntityState.Deleted;
            foreach (Employee e in it.Employees)
                e.EntityState = EntityState.Deleted;
            businessLayer.RemoveDepartment(it);

            Console.ReadLine();
        }
    }
}

Output


159 Comments on “Implementing a generic data access layer using Entity Framework”

  1. michael wassermann says:

    Has this been updated to work with EF6 and 7 and is their source code?

  2. michael wassermann says:

    Is their and updated version. that supports EF6 and 7

  3. Shahin says:

    hello

    the business logic layer is independent of each layer and you should not refer to data access layer if after a period of time you decided to change the way of access to the database you must change the business layer, inverse your dependency (Dependency Injection) and the entities in Data Access Layer is different from your business objects… please implement your generic data access in business logic layer, and concrete generic access class in your data access layer so that the business logic is going to be independent of the lower layer after that pass the business objects to generic data access interface and in the Concrete data access class you should get the result from EF and then convert them all to business object as result.

  4. Lakmal Perera says:

    Hi,

    i hope this solution is work fine for master data, how about transnational data ?

  5. Moody says:

    Another not working example

  6. Alberto says:

    Hi,
    have you thought about making it asynchronous?

  7. Martin Moesby Petersen says:

    Great article – even with all these years :) But I have two questions:

    1) How would you remove an related entity without deleting the entity, but just the relationship between a parent and child entity?

    2) How would you handle many-2-many relationships? Adding is no problem, but removing the relationship between two entities gives me issues… I cannot remove the relationship without deleting the entity, which is not what I want – I just want to remove the relationship between entities.

  8. Sohail Khan says:

    Hi Magnus,
    Can you please help regarding how to get multiple related entity like if we have a doctor entity then we have 2 more entities like education and experience relate to doctor.

  9. Arend Jan says:

    Thanks a million!!! Clearly written, very helpful.


Leave a comment