在基本存储库类的构造函数中配置DBContext [英] Configuring DBContext in the constructor of my base repository class

查看:73
本文介绍了在基本存储库类的构造函数中配置DBContext的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的解决方案启动后,我需要实例化我的DBContext.我问了这个问题,它表明我可以使用构造函数参数来做到这一点.. >

有人建议我以此为例:

var connection = @"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);

using (var context = new BloggingContext(optionsBuilder.Options))
{
   // do stuff
}

但是,我已经实现了存储库模式(无论是好是坏),并考虑到我的更改情况-在解决方案运行启动后才具有连接字符串-我需要将其实现到基本存储库类中,并且我处于有点损失.

目前我有这个:

    public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{

    public JobsLedgerAPIContext _context;

    #region Properties
    public EntityBaseRepository(JobsLedgerAPIContext context)
    {
        _context = context;
    }
    #endregion
    public virtual IQueryable<T> GetAll()
    {
        return _context.Set<T>().AsQueryable();
    }

    public virtual int Count()
    {
        return _context.Set<T>().Count();
    }
     ......

我如何实现此更改,既要在构造函数中实例化DBContext(通过绕过启动时将上下文添加为服务的需要),又要用"using"等包装每个虚拟方法,

编辑. Camilo表示我没有数据库名称的身份.

基本情况是系统启动(这是与该问题无关的Aurelia SPA项目),将程序包发送到显示登录屏幕的浏览器.用户登录.通过JWT控制器验证用户.在控制器中验证之后(使用具有一个包含3个字段的表的目录数据库-用户名,密码,数据库名称),我使用数据库名称创建连接字符串,并然后在那时实例化我的DBContext ..这样就通过构造函数.

下面的答案需要修改,因为带有该答案的工厂答案(有前途的)存在此

我进一步假设我不能再将服务注入到控制器中,而是一旦我确定了连接字符串并将该连接字符串传递给服务后就对其进行了更新.

此外,鉴于我现在已经在启动时添加了一个单例,是否需要删除原始条目? :

        services.AddDbContext<JobsLedgerAPIContext>(options => options.
          UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("JobsLedger.API")));

有效地将其替换为我的单例引用,如下所示:

services.AddSingleton(typeof(IContextFactory<>),typeof(ContextFactory<>));

解决方案

已编辑

已编辑答案,以纠正发现的错误并 由Nkosi 固定.谢谢@Nkosi.

实施工厂模式.您可以创建一个工厂,将其命名为ContextFactory,如下所示:

首先,定义接口. 进一步修改,删除了connectionString参数

public interface IContextFactory<T> where T : DbContext
{
    T CreateDbContext();
}

创建一个实现此接口的工厂类(根据Nkosi

然后修改您的基本存储库并保护JobsLedgerAPIContext.该上下文将由派生类设置. 进一步修改以删除构造函数.它将使用无参数的构造函数.

public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
    protected JobsLedgerApiContext Context { get; set; }

    public virtual IQueryable<T> GetAll()
    {
        return Context.Set<T>().AsQueryable();
    }

    public virtual int Count()
    {
        return Context.Set<T>().Count();
    }
}

将派生类更改为使用IContextFactory. 进一步修改为使用_contextFactory.CreateDbContext()参数少方法

IClientRepository应该定义了SetContext方法.

public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
    private readonly IContextFactory<JobsLedgerApiContext> _contextFactory;

    public ClientRepository(IContextFactory<JobsLedgerApiContext> factory)
    {
        _contextFactory = factory;
    }

    // this method will set the protected Context property using the context
    // created by the factory
    public void SetContext()
    {
        Context = _contextFactory.CreateDbContext();
    }

    public void RelatedSuburbEntities(Suburb suburb)
    {
        Context.Entry(suburb).Reference<State>(a => a.State).Load();
    }
}

在接收IClientRepository实例的控制器中,您可以在HttpContext.Items中设置连接,该连接对请求有效.然后,ContextFactory使用IHttpContextAccessor检索该值.然后,您只需在存储库中调用_repository.SetContext();方法.

public class HomeController : Controller
{
    private readonly IClientRepository _repository;

    public HomeController(IClientRepository repository)
    {
        _repository = repository;
    }

    public IActionResult Index()
    {
       // save the connectionString in the HttpContext.Items
       HttpContext.Items["connection-string"] = "test-connection";

       // set the context 
       _repository.SetContext();

       return View();
    }
}

确保在ConfigureServices中将IContextFactory注册为开放的泛型,并按如下所示将其注册为Singleton,还要注册HttpContextAccessor和IClientRepository

services.AddHttpContextAccessor();
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
services.AddTransient<IClientRepository, ClientRepository>();

I have a situation where I need to instantiate my DBContext after my solution has started up. I asked this question which indicated that I could do this with a constructor argument.

It was suggested that I implement as an example this:

var connection = @"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);

using (var context = new BloggingContext(optionsBuilder.Options))
{
   // do stuff
}

However I have implemented the repository pattern (for better or worst) and given my changed circumstances - not having a connection string until after the solution has run startup - I need to implement this into the base repository class and I am at a bit of a loss..

Currently I have this:

    public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{

    public JobsLedgerAPIContext _context;

    #region Properties
    public EntityBaseRepository(JobsLedgerAPIContext context)
    {
        _context = context;
    }
    #endregion
    public virtual IQueryable<T> GetAll()
    {
        return _context.Set<T>().AsQueryable();
    }

    public virtual int Count()
    {
        return _context.Set<T>().Count();
    }
     ......

How do I implement this change both instantiating the DBContext in the constructor (there by bypassing the need to add the context as a service in startup) and then with the wrapping each of the virtual methods with "using" etc

EDIT.. Camilo indicated I had not identified when I have the database name.

The basic situation is that the system starts up (This is an Aurelia SPA project which is irrelevant to this issue) sends the package to the browser which shows a login screen. User logs in.. User is verified via a JWT controller.. Once verified in the controller (using a catalog database that has one table with 3 fields - username, password, database name) I use the database name to create a connection string and then instantiate my DBContext at that point.. so via a constructor.

The answers below need to be modified as the one with the factory answer (promising) has errors as discovered by this question.. Nkosi responded with an great answer to the error.

EDIT 2.. This is a response to the edited question below:

Here is my original Client Repository with :base(context) on the constructor.

using JobsLedger.DATA.Abstract;
using JobsLedger.MODEL.Entities;

namespace JobsLedger.DATA.Repositories
{
    public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
    {
        private new JobsLedgerAPIContext _context;

        public ClientRepository(JobsLedgerAPIContext context) : base(context)
        {
            _context = context;
        }

        public void RelatedSuburbEntities(Suburb _suburb)
        {
            _context.Entry(_suburb).Reference<State>(a => a.State).Load();
        }
    }
}

It has a reference to the base class "context". I am not sure how to modify this given that I believe I still need that ":base(context)" at the end. As well, I have a method in this that accesses _context as well which is part of the constructor...

Further I assume that I can no longer inject the service into the controller but instead new it up once I have secured the connection string and then pass that connection string to service.

Also, Given I have now added a singleton on the startup do I need to remove the original entry? :

        services.AddDbContext<JobsLedgerAPIContext>(options => options.
          UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("JobsLedger.API")));

effectively replacing it with my singleton reference as per below:

services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));

解决方案

Edited

The answer has been edited to rectify the mistake spotted and fixed by Nkosi. Thanks, @Nkosi.

Implement a factory pattern. You can create a factory, call it ContextFactory as below:

First, define the interface. Further modified, removed the connectionString parameter

public interface IContextFactory<T> where T : DbContext
{
    T CreateDbContext();
}

Create a factory class that implements this interface (edited as per Nkosi answer). Further modified to inject IHttpContextAccessor

public class ContextFactory<T> : IContextFactory<T> where T : DbContext
{
    private readonly HttpContext _httpContext;

    public ContextFactory(IHttpContextAccessor contextAccessor)
    {
        _httpContext = contextAccessor.HttpContext;
    }

    public T CreateDbContext()
    {
        // retreive the connectionString from the _httpContext.Items
        // this is saved in the controller action method
        var connectionString = (string)_httpContext.Items["connection-string"];
        var optionsBuilder = new DbContextOptionsBuilder<T>();
        optionsBuilder.UseSqlServer(connectionString);
        return (T)Activator.CreateInstance(typeof(T), optionsBuilder.Options);
    }
}

Then modify your base repository and make the JobsLedgerAPIContext protected. This context is going to be set by the derived class. Further modified to remove the constructor. It will use the parameterless constructor.

public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
    protected JobsLedgerApiContext Context { get; set; }

    public virtual IQueryable<T> GetAll()
    {
        return Context.Set<T>().AsQueryable();
    }

    public virtual int Count()
    {
        return Context.Set<T>().Count();
    }
}

Change your derived class to use IContextFactory. Further modified to use the _contextFactory.CreateDbContext() parameter less method

The IClientRepository should have SetContext method defined.

public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
    private readonly IContextFactory<JobsLedgerApiContext> _contextFactory;

    public ClientRepository(IContextFactory<JobsLedgerApiContext> factory)
    {
        _contextFactory = factory;
    }

    // this method will set the protected Context property using the context
    // created by the factory
    public void SetContext()
    {
        Context = _contextFactory.CreateDbContext();
    }

    public void RelatedSuburbEntities(Suburb suburb)
    {
        Context.Entry(suburb).Reference<State>(a => a.State).Load();
    }
}

In the controller, that receives IClientRepository instance, you can set the connection in the HttpContext.Items, which will be valid for the request. This value will then be retrieved by the ContextFactory using IHttpContextAccessor. Then you simply call the _repository.SetContext(); method on the repository.

public class HomeController : Controller
{
    private readonly IClientRepository _repository;

    public HomeController(IClientRepository repository)
    {
        _repository = repository;
    }

    public IActionResult Index()
    {
       // save the connectionString in the HttpContext.Items
       HttpContext.Items["connection-string"] = "test-connection";

       // set the context 
       _repository.SetContext();

       return View();
    }
}

Make sure you register the IContextFactory in ConfigureServices as open generics and Singleton as below, also register the HttpContextAccessor and IClientRepository

services.AddHttpContextAccessor();
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
services.AddTransient<IClientRepository, ClientRepository>();

这篇关于在基本存储库类的构造函数中配置DBContext的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆