HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Database first entity framework, repository, service, UnitOfWork pattern

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
databasefirstservicerepositoryunitofworkframeworkpatternentity

Problem

I have seperated my project into two projects. One is the web forms project and the other is the Model project with contains a generic repository implementation, a service class for each of my entities and a UnitOfWork class. All the code below apart from the last section is in my Model class library project.

Generic repository interface

public interface IRepository : IDisposable where TEntity : class
{
    IQueryable GetAll();
    IQueryable GetWhere(Expression> predicate);
    TEntity GetSingle(Expression> predicate);
    TEntity GetSingleOrDefault(Expression> predicate);

    void Add(TEntity entity);
    void Delete(TEntity entity);
}


I start of with this Interface which my services will interact with.

A few points about this class:

  • As i am having a service for each of my entity object, I see no reason why I shouldn't use a generic repository as creating a repository for each entity object as-well, would bloat the amount of classes i have and make it harder to manage, when i can just pass a predicate from my service into the Get methods to retrieve the data i need. So instead of creating multiple methods such as "GetById", "GetByName" in a repository specific to each entity i use a generic one and include those methods in the service class. However i have heard many people say the generic repository is an anti-pattern so i'm not sure if this is the right way.



  • I do not include a save changes methods in the repositories as i want to make any interactions with these only come from the UnitOfWork class (which will expose the service classes as properties). So this should force validation to be done via the service classes as these are the only objects which will interact with repository. In every example i see the repository has a save changes method so i'n not sure if this is a reasonable approach.



Generic repository

Below is the implementation of the IRepository for my web app, I think it has a pretty standard structure. I swap this out for a

Solution

I've used the Generic repository solution myself in the past. At first I found it really great but after a while as the project got a bit bigger it started to feel a bit restrictive and didn't seem to offer much benefits for the amount of code written.

The more I read the more it appeard Entity framework was already a Repository / UnitofWork. Why did I need another layer? Well I'm sure there are plenty of reasons why but in my projects I couldn't see it.

Instead I found myself starting to use the entity-framework directly within a service layer but abstracting the EF away using interfaces. This allowed me to remove the Repository layer whilst maintaining the level of abstraction I was after between the UI layer and any business or data layer (as well as continuing to easily mock for unit tests).

Using your solution an alternative approach I might consider would be something like:

public interface IUnitOfWork
{
   public IDbSet Groups { get; set; }
   // other entity sets here

   System.Data.Entity.Database Database { get; }
   DbEntityEntry Entry(TEntity entity) where TEntity : class;
   int SaveChanges();
}


My Unit of work was essentially my Entity framework data context

public class MyDbContext : DbContext, IUnitOfWork
{
   public DbSet Groups { get; set; }

   // etc
}


I would now only use a service layer directly interacting with the IUnitOfWork. However I would also consider abstracting each service behind an interface itself.

public interface IGroupService : ICrudService // Maybe, maybe not depending
{
    Group GetById(int id);
}


If there were a group of interfaces that shared a common theme i.e. CRUD I might consider making those seperate and implementing them where necessary. Or even a base service class to contain common methods.

public interface ICrudService
{
   TEntity GetById(int id);
   void Update(TEntity entity);
   void Add(TEntity entity);
   void Delete(TEntity entity);
}

public class GroupService : IGroupService
{
   private readonly IUnitOfWork _unitOfWork;

   public GroupService(IUnitOfWork unitOfWork)
   {
      _unitOfWork = unitOfWork;
   } 

   public Group GetById(int id)
   {
      return _unitOfWork.Find(id);
   }
}


If my service required other services I would simply pass those into the constructor. If I found myself passing too many then I would start knowing I had a bit of SRP mis-use and consider creating another service or re-arranging my existing code base.

If you wanted a GOD service class you could still achieve this as you did with your UnitOfWork example by doing something like:

public interface IServiceContext
{
   IGroupService GroupService { get; private set; }
   // And all your other services
}


Hopefully you get the drift....

Code Snippets

public interface IUnitOfWork
{
   public IDbSet<Group> Groups { get; set; }
   // other entity sets here

   System.Data.Entity.Database Database { get; }
   DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
   int SaveChanges();
}
public class MyDbContext : DbContext, IUnitOfWork
{
   public DbSet<Group> Groups { get; set; }

   // etc
}
public interface IGroupService : ICrudService<Group> // Maybe, maybe not depending
{
    Group GetById(int id);
}
public interface ICrudService<TEntity>
{
   TEntity GetById(int id);
   void Update(TEntity entity);
   void Add(TEntity entity);
   void Delete(TEntity entity);
}

public class GroupService : IGroupService
{
   private readonly IUnitOfWork _unitOfWork;

   public GroupService(IUnitOfWork unitOfWork)
   {
      _unitOfWork = unitOfWork;
   } 

   public Group GetById(int id)
   {
      return _unitOfWork.Find(id);
   }
}
public interface IServiceContext
{
   IGroupService GroupService { get; private set; }
   // And all your other services
}

Context

StackExchange Code Review Q#47865, answer score: 5

Revisions (0)

No revisions yet.