简体   繁体   中英

Implementing a Simple Repository, Unit of Work with Dependency Injection

I have been trying to create a Repository Pattern along with Dependency injection, But Looks like I am missing some simple step. Here is my code

public class HomeController 
{
   private readonly ILoggingRepository _loggingRepository;
   public HomeController(ILoggingRepository loggingRepository)
   {
     _loggingRepository = loggingRepository;
   }

   public void MyMethod()
   {
        string message = "MyMessage Called";
       _loggingRepository .LogMessage(message);
    }
}

// ILoggingRepository.cs
public interface ILoggingRepository
{
   void LogMessage(string message);
}

// LoggingRepository.cs
public class LoggingRepository : ILoggingRepository
{ 
     public void LogMessage(string message)
     {
        using (var dbContext = new DbContext())
        {
            var serviceLog = new Log() { Message = message, Logged = DateTime.UtcNow };
            dbContext.Logs.Add(serviceLog);
            dbContext.SaveChanges();
        }
   }
}

This works perfectly all right so far, but the problem arises when i make more than one repository calls.

Now I know that Entity framework 6.0 has inbuilt unit of work representation so I didn't created a UnitofWork Interface or class But the problem appears when I do something like this in two different transactions. Lets say

Area area = _areaRepository.GetArea(); // Line 1
area.Name = "NewArea"; // Line 2
_areaRepository.SaveArea(area);  // Line 3   

now because it _areaRepository creates a new DbContext in Line 3, it doesn't changes the name of area as it doesn't consider EntityState.Modified I have to explicitly set that, which isn't correct.

So I guess I need to do all this in single Transaction, Where I am doing wrong here ? What is the correct and best way to achieve this, Should I inject my DbContext also into the repository?

This is how I doit all times: If dont use Repository or Unit of Work layers, because Entity Framework db Context already implements those patterns. So, I only have a Service layer:

public interface IBaseService<VO, ENT>{
    IQueryable<VO> GetAll();

    VO Get(object id);
}

public abstract class BaseService<VO, ENT> : IBaseService<VO, ENT>{

    MyContext db;

    public BaseService(MyContext db){
        this.db = db;
    }

    public IQueryable<VO> GetAll(){
        return db.Set<ENT>().ProjectTo<VO>();
    }
}

A service class have a dbContext injected in the constructor. This classes are located in a Service library. Then, how the dbContext and the service are resolved is a problem of the project who will be using them. The ProjectTo method is an extension for IQueryable from the Automapper Nuget. For example:

A Windows Service needs all services instance in the same thread shares the same dbContext. So, in the windows service project, I use Ninject https://www.nuget.org/packages/Ninject/4.0.0-beta-0134 , this library is a dependency resolver, wich I use to configure how dependencies are builded, creating a Kernel, like this:

var kernel = new StandardKernel();

kernel.Bind<MyContext>().ToSelf().InThreadScope();
kernel.Bind<IServiceImplInterface>().To<ServiceImplClass>().InThreadScope();

I you are creating a Web project, you will need to install a aditional nuget (Ninject.WebCommon, Ninject.Web.COmmon.WebHost, Ninject.MVC5) to provide a .InRequestScope() method to the binding configuration, like this:

var kernel = new StandardKernel();

kernel.Bind<MyContext>().ToSelf().InRequestScope();
kernel.Bind<IServiceImplInterface>().To<ServiceImplClass>().InRequestScope();

You need setup those kernel when the app startup. In a web project is in the global.asax, in a windows service project, should be in the Service constructor:

You can visit www.ninject.org/learn.html to learn more about ninject. But, there are othres like Autofac or Caste Windsor, it is up to you. If you like to keep using the repository pattern, just use Ninject inject them into the Service layer, like i did with the dbContext.

The best approach is to have one instance of DbContext , injecting it on each repository implementation. That way you will have a single instance of the database context, so EF will be able to detect changes on the entity objects.

If you need to use isolated dbContexts as in your example, then you need to explicitly set the state of the object as Modified .

Depending on the type of project, you should set the context on a specific scope. For example, for web applications one option is to use instance per Web request (per lifetime scope). Check this url where you can see a good explanation of the different instance scopes.

The using statement simply creates a new scope, executing the Dispose() method after the code block. EF does a lot on the background to maintain the UoW and state of the objects, but in your case, with the using, you are not using this fature.

First, a DbContext is a repository. If you want to wrap it in a custom repository, they should have the same lifecycle.

Second, your Unit-of-work is your controller. The repository should be scoped to unit-of-work.

This means that your repository needs to be Disposable, since the DbContext is.

So something like:

    public interface ILoggingRepository : IDisposable
    {
        void LogMessage(string message);
    }

    // LoggingRepository.cs
    public class LoggingRepository : ILoggingRepository
    {
        MyDbContext db;
        public LoggingRepository(MyDbContext db)
        {
            this.db = db;
        }

        public void Dispose()
        {
            db.Dispose();
        }

        public void LogMessage(string message)
        {

            var serviceLog = new MonitoringServiceLog() { Message = message, Logged = DateTime.UtcNow };
            db.MonitoringServiceLogs.Add(serviceLog);
            db.SaveChanges();

        }
    }

If your ILoggingRepository wan't a database, it might be a file or something else that is expensive to create or open and needs to be closed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM