实现一个简单的存储库,具有依赖注入的工作单元

时间:2017-05-15 11:23:17

标签: c# entity-framework dependency-injection repository-pattern unit-of-work

我一直在尝试创建存储库模式以及依赖注入,但看起来我错过了一些简单的步骤。这是我的代码

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();
        }
   }
}

到目前为止,这完全可以正常工作,但是当我进行多个存储库调用时会出现问题。

现在我知道Entity framework 6.0有内置的工作单元表示,所以我没有创建UnitofWork接口或类 但是当我在两个不同的交易中做这样的事情时,问题出现了。让我们说

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

现在因为_areaRepository在第3行创建了一个新的DbContext,它不会更改区域的名称,因为它不考虑EntityState.Modified 我必须明确设置,这是不正确的。

所以我想我需要在单个交易中完成所有这些,我在这里做错了吗? 实现这一目标的正确和最佳方法是什么,我是否应该将DbContext注入存储库?

3 个答案:

答案 0 :(得分:0)

这就是我一直这样做的方式: 如果不使用Repository或Unit of Work层,因为Entity Framework db Context已经实现了这些模式。所以,我只有一个服务层:

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>();
    }
}

服务类在构造函数中注入了dbContext。这些类位于服务库中。然后,如何解析dbContext和服务是项目谁将使用它们的问题。 ProjectTo方法是Automapper Nuget的IQueryable扩展。例如:

Windows服务需要同一线程中的所有服务实例共享相同的dbContext。所以,在windows服务项目中,我使用Ninject https://www.nuget.org/packages/Ninject/4.0.0-beta-0134,这个库是一个依赖项解析器,我用它来配置如何构建依赖项,创建一个内核,如下所示:

var kernel = new StandardKernel();

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

我正在创建一个Web项目,您需要安装一个aditional nuget(Ninject.WebCommon,Ninject.Web.COmmon.WebHost,Ninject.MVC5)来为绑定配置提供.InRequestScope()方法,如这样:

var kernel = new StandardKernel();

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

您需要在应用启动时设置这些内核。在一个web项目中,在global.asax中,在一个Windows服务项目中,应该在Service构造函数中:

您可以访问www.ninject.org/learn.html了解有关ninject的更多信息。但是,有像Autofac或Caste Windsor这样的东西,它取决于你。如果您想继续使用存储库模式,只需使用Ninject将它们注入服务层,就像我使用dbContext一样。

答案 1 :(得分:-1)

最好的方法是拥有一个DbContext实例,将其注入每个存储库实现。这样,您将拥有数据库上下文的单个实例,因此EF将能够检测实体对象的更改。

如果您需要在示例中使用隔离的dbContexts,则需要将对象的状态显式设置为Modified

根据项目类型,您应该在特定范围内设置上下文。例如,对于Web应用程序,一个选项是使用每个Web请求的实例(每个生命周期范围)。检查this url,您可以在其中查看不同实例范围的详细说明。

using语句只是创建一个新范围,在代码块之后执行Dispose()方法。 EF在后台做了很多工作来维护对象的UoW和状态,但在你的情况下,使用时,你没有使用这个功能。

答案 2 :(得分:-2)

首先,DbContext 一个存储库。如果要将其包装在自定义存储库中,它们应具有相同的生命周期。

其次,您的工作单位是您的控制者。存储库应限定为工作单元。

这意味着您的存储库需要是Disposable,因为DbContext是。

类似于:

    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();

        }
    }

如果你的ILoggingRepository不是一个数据库,它可能是一个文件或其他一些昂贵的创建或打开的文件,需要关闭。