I am about to implement an Entity Framework 6 design with a repository and unit of work.
There are so many articles around and I'm not sure what the best advice is: For example I realy like the pattern implemented here: for the reasons suggested in the article here
However, Tom Dykstra (Senior Programming Writer on Microsoft's Web Platform & Tools Content Team)
suggests it should be done in another article: here
I subscribe to Pluralsight
, and it is implemented in a slightly different way pretty much every time it is used in a course so choosing a design is difficult.
Some people seem to suggest that unit of work is already implemented by DbContext
as in this post, so we shouldn't need to implement it at all.
I realise that this type of question has been asked before and this may be subjective but my question is direct:
I like the approach in the first (Code Fizzle) article and wanted to know if it is perhaps more maintainable and as easily testable as other approaches and safe to go ahead with?
Any other views are more than welcome.
No, the repository/unit-of-work pattern (shortened to Rep/UoW) isn't useful with EF Core. EF Core already implements a Rep/UoW pattern, so layering another Rep/UoW pattern on top of EF Core isn't helpful.
That is, it's dead if you are using Entity Framework Core. If you're still using straight ADO.NET, or even just dapper, then the repository pattern still probably makes sense for you.
The main advantage to use the repository design pattern is to isolate the data access logic and business logic. So that if we do any changes in any of this logic, then that should affect other logic. So let us discuss the step by step procedure to implement the repository pattern in C#.
They decouple application and domain design from persistence technology, multiple database strategies, or even multiple data sources.
@Chris Hardie is correct, EF implements UoW out of the box. However many people overlook the fact that EF also implements a generic repository pattern out of the box too:
var repos1 = _dbContext.Set<Widget1>(); var repos2 = _dbContext.Set<Widget2>(); var reposN = _dbContext.Set<WidgetN>();
...and this is a pretty good generic repository implementation that is built into the tool itself.
Why go through the trouble of creating a ton of other interfaces and properties, when DbContext gives you everything you need? If you want to abstract the DbContext behind application-level interfaces, and you want to apply command query segregation, you could do something as simple as this:
public interface IReadEntities { IQueryable<TEntity> Query<TEntity>(); } public interface IWriteEntities : IReadEntities, IUnitOfWork { IQueryable<TEntity> Load<TEntity>(); void Create<TEntity>(TEntity entity); void Update<TEntity>(TEntity entity); void Delete<TEntity>(TEntity entity); } public interface IUnitOfWork { int SaveChanges(); }
You could use these 3 interfaces for all of your entity access, and not have to worry about injecting 3 or more different repositories into business code that works with 3 or more entity sets. Of course you would still use IoC to ensure that there is only 1 DbContext instance per web request, but all 3 of your interfaces are implemented by the same class, which makes it easier.
public class MyDbContext : DbContext, IWriteEntities { public IQueryable<TEntity> Query<TEntity>() { return Set<TEntity>().AsNoTracking(); // detach results from context } public IQueryable<TEntity> Load<TEntity>() { return Set<TEntity>(); } public void Create<TEntity>(TEntity entity) { if (Entry(entity).State == EntityState.Detached) Set<TEntity>().Add(entity); } ...etc }
You now only need to inject a single interface into your dependency, regardless of how many different entities it needs to work with:
// NOTE: In reality I would never inject IWriteEntities into an MVC Controller. // Instead I would inject my CQRS business layer, which consumes IWriteEntities. // See @MikeSW's answer for more info as to why you shouldn't consume a // generic repository like this directly by your web application layer. // See http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91 and // http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92 for more info // on what a CQRS business layer that consumes IWriteEntities / IReadEntities // (and is consumed by an MVC Controller) might look like. public class RecipeController : Controller { private readonly IWriteEntities _entities; //Using Dependency Injection public RecipeController(IWriteEntities entities) { _entities = entities; } [HttpPost] public ActionResult Create(CreateEditRecipeViewModel model) { Mapper.CreateMap<CreateEditRecipeViewModel, Recipe>() .ForMember(r => r.IngredientAmounts, opt => opt.Ignore()); Recipe recipe = Mapper.Map<CreateEditRecipeViewModel, Recipe>(model); _entities.Create(recipe); foreach(Tag t in model.Tags) { _entities.Create(tag); } _entities.SaveChanges(); return RedirectToAction("CreateRecipeSuccess"); } }
One of my favorite things about this design is that it minimizes the entity storage dependencies on the consumer. In this example the RecipeController
is the consumer, but in a real application the consumer would be a command handler. (For a query handler, you would typically consume IReadEntities
only because you just want to return data, not mutate any state.) But for this example, let's just use RecipeController
as the consumer to examine the dependency implications:
Say you have a set of unit tests written for the above action. In each of these unit tests, you new up the Controller, passing a mock into the constructor. Then, say your customer decides they want to allow people to create a new Cookbook or add to an existing one when creating a new recipe.
With a repository-per-entity or repository-per-aggregate interface pattern, you would have to inject a new repository instance IRepository<Cookbook>
into your controller constructor (or using @Chris Hardie's answer, write code to attach yet another repository to the UoW instance). This would immediately make all of your other unit tests break, and you would have to go back to modify the construction code in all of them, passing yet another mock instance, and widening your dependency array. However with the above, all of your other unit tests will still at least compile. All you have to do is write additional test(s) to cover the new cookbook functionality.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With