EF and repository pattern - ending up with multiple DbContexts in one controller - any issues (performance, data integrity)?

后端 未结 3 693
一个人的身影
一个人的身影 2020-12-28 10:37

Most of my knowledge of ASP.NET MVC 3 comes from reading through the book Pro ASP.NET MVC 3 Framework by Adam Freeman and Steven Senderson. For my test application I have tr

相关标签:
3条回答
  • 2020-12-28 10:47

    As promised I post my solution.

    I came across your question because I was having trouble with the IIS application pool memory growing beyond limits and having multiple DBContexts was one of my suspects. In retrospect it is fair to say that there were other causes for my trouble. However, it challenged me to find a better layer based design for my repository.

    I found this excellent blog: Correct use of Repository and Unit Of Work patterns in ASP.NET MVC leading me to the right direction. The redesign is based on the UnitOfWork pattern. It enables me to have just one constructor parameter for all my controllers instead of "never ending constructor parameters". And after that, I was able to introduce proactive caching as well, which solved a great deal of the earlier mentioned trouble I was having.

    Now I only have these classes:

    • IUnitOfWork
    • EFUnitOfWork
    • IGenericRepository
    • EFGenericRepository

    See the referred blog for complete information and implementation of these classes. Just to give an example, IUnitOfWork contains repository definitions for all entities that I need, like:

    namespace MyWebApp.Domain.Abstract
    {
      public interface IUnitOfWork : IDisposable
      {
        IGenericRepository<AAAAA> AAAAARepository { get; }
        IGenericRepository<BBBBB> BBBBBRepository { get; }
        IGenericRepository<CCCCC> CCCCCRepository { get; }
        IGenericRepository<DDDDD> DDDDDRepository { get; } 
        // etc.
    
        string Commit();
      }
    }
    

    The Dependency Injection (DI) is just one statement (I use Ninject):

    ninjectKernel.Bind<IUnitOfWork>().To<EFUnitOfWork>();
    

    The Controllers-constructors are maintainable:

    public class MyController : BaseController
    {
      private MyModel mdl = new MyModel();
    
      private IUnitOfWork _context; 
    
      public MyController(IUnitOfWork unitOfWork)
      {
        _context = unitOfWork;
    
        // intialize whatever needs to be exposed to the View:
        mdl.whatever = unitOfWork.SomeRepository.AsQueryable(); 
      }
    
     // etc.
    

    Within the Controller I can use _context to access all repositories, if needed. The nice part of it, is that it needs just a single Commit()-call to save changed data for all repositories:

    _context.Commit();
    
    0 讨论(0)
  • 2020-12-28 10:53

    Do I risk ending up with multiple DbContexts within one request?

    Yes. Each instance of a repository is going to instantiate its own DbContexts instances. Depending on the size and use of the application, this may not be a problem although it is not a very scalable approach. There are several ways of handling this though. In my web projects I add the DbContext(s) to the Request's Context.Item collection, this way it is available to all classes that require it. I use Autofac (similar to Ninject) to control what DbContexts are created within specific scenarios and how they are stored, e.g. I have a different 'session manager' for a WCF context to the one for a Http context.

    And would that lead to any significant performance overhead?

    Yes, but again not massively if the application is relatively small. As it grows though, you may notice the overhead.

    How about a potential for conflicts between the contexts and any consequences to the data integrity?

    One of the reasons for using an ORM like this is so that changes can be maintained within the DbContext. If you are instantiating multiple context instances per request you lose this benefit. You wouldn't notice conflicts or any impact of the integrity per se unless you were handling a lot of updates asynchronously.

    0 讨论(0)
  • 2020-12-28 11:07

    There won't be a performance problem (unless we are talking about nanoseconds, instantiating a context is very cheap) and you won't have damaged your data integrity (before that happens you'll get exceptions).

    But the approach is very limited and will work only in very simple situations. Multiple contexts will lead to problems in many scenarios. As an example: Suppose you want to create a new child for an existing parent and would try it with the following code:

    var parent = parentRepo.TestParents.Single(p => p.Id == 1);
    var child = new Child { TestParent = parent };
    childrenRepo.SaveTestChild(child);
    

    This simple code won't work because parent is already attached to the context inside of parentRepo but childrenRepo.SaveTestChild will try to attach it to the context inside of childrenRepo which will cause an exception because an entity must not be attached to another context. (Here is actually a workaround because you could set the FK property instead of loading the parent: child.TestParentID = 1. But without a FK property it would be a problem.)

    How to solve such a problem?

    One approach could be to extend the EFDbTestChildRepository by a new property:

    public IQueryable<TestParent> TestParents
    {
        get { return context.TestParents; }
    }
    

    In the example code above you could then use only one repository and the code would work. But as you can see, the name "EFDbTest Child Repository" doesn't really fit anymore to the purpose of the new repository. It should be now "EFDbTest ParentAndChild Repository".

    I would call this the Aggregate Root approach which means that you create one repository not for only one entity but for a few entities which are closely related to each other and have navigation properties between them.

    An alternative solution is to inject the context into the repositories (instead of creating it in the repositories) to make sure that every repository uses the same context. (The context is often abstracted into a IUnitOfWork interface.) Example:

    public class MyController : Controller
    {
        private readonly MyContext _context;
        public MyController()
        {
            _context = new MyContext();
        }
    
        public ActionResult SomeAction(...)
        {
            var parentRepo = new EFDbTestParentRepository(_context);
            var childRepo = new EFDbTestChildRepository(_context);
    
            //...
        }
    
        protected override void Dispose(bool disposing)
        {
            _context.Dispose();
            base.Dispose(disposing);
        }
    }
    

    This gives you a single context per controller you can use in multiple repositories.

    The next step might be to create a single context per request by dependency injection, like...

    private readonly MyContext _context;
    public MyController(MyContext context)
    {
        _context = context;
    }
    

    ...and then configuring the IOC container to create a single context instance which gets injected into perhaps multiple controllers.

    0 讨论(0)
提交回复
热议问题