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

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

问题描述

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

有人建议我以这样的方式实现:

var connection = @"Server=(localdb)mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";var optionsBuilder = new DbContextOptionsBuilder();optionsBuilder.UseSqlServer(connection);使用 (var context = new BloggingContext(optionsBuilder.Options)){//做东西}

但是,我已经实现了存储库模式(无论好坏)并考虑到我的情况发生变化 - 在解决方案运行启动之前没有连接字符串 - 我需要将其实现到基本存储库类中,并且我处于有点亏..

目前我有这个:

 公共类 EntityBaseRepository:IEntityBaseRepository其中 T : 类、IEntityBase、new(){公共 JobsLedgerAPIContext _context;#region 属性公共 EntityBaseRepository(JobsLedgerAPIContext 上下文){_context = 上下文;}#endregion公共虚拟 IQueryable得到所有(){返回 _context.Set().AsQueryable();}公共虚拟 int Count(){返回 _context.Set().Count();}......

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

编辑.. Camilo 表示我没有确定数据库名称.

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

下面的答案需要修改,因为工厂答案(有希望的)有错误,因为这个问题.. Nkosi 对错误做出了很好的回答.

编辑 2..这是对以下已编辑问题的回复:

这是我的原始客户端存储库,在构造函数上带有 :base(context).

使用 JobsLedger.DATA.Abstract;使用 JobsLedger.MODEL.Entities;命名空间 JobsLedger.DATA.Repositories{公共类 ClientRepository : EntityBaseRepository, IClientRepository{私有的新 JobsLedgerAPIContext _context;公共 ClientRepository(JobsLedgerAPIContext 上下文):基础(上下文){_context = 上下文;}公共无效相关郊区实体(郊区_郊区){_context.Entry(_suburb).Reference(a => a.State).Load();}}}

它引用了基类上下文".我不知道如何修改这个,因为我相信我最后仍然需要:base(context)".同样,我有一个方法可以访问 _context 以及它是构造函数的一部分...

此外,我假设我不能再将服务注入控制器,而是在确保连接字符串安全后将其更新,然后将该连接字符串传递给服务.

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

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

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

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

解决方案

已编辑

<块引用>

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

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

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

公共接口 IContextFactory其中 T : DbContext{T CreateDbContext();}

创建一个实现这个接口的工厂类(根据 Nkosi 答案).进一步修改为注入 IHttpContextAccessor

公共类ContextFactory: IContextFactory其中 T : DbContext{私有只读 HttpContext _httpContext;公共上下文工厂(IHttpContextAccessor contextAccessor){_httpContext = contextAccessor.HttpContext;}公共 T CreateDbContext(){//从 _httpContext.Items 中检索 connectionString//这保存在控制器动作方法中var connectionString = (string)_httpContext.Items["connection-string"];var optionsBuilder = new DbContextOptionsBuilder();optionsBuilder.UseSqlServer(connectionString);返回 (T)Activator.CreateInstance(typeof(T), optionsBuilder.Options);}}

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

公共类EntityBaseRepository:IEntityBaseRepository其中 T : 类、IEntityBase、new(){受保护的 JobsLedgerApiContext 上下文 { 获取;放;}公共虚拟 IQueryable得到所有(){返回 Context.Set().AsQueryable();}公共虚拟 int Count(){返回 Context.Set().Count();}}

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

IClientRepository 应该定义了 SetContext 方法.

公共类 ClientRepository : EntityBaseRepository, IClientRepository{private readonly IContextFactory_contextFactory;公共 ClientRepository(IContextFactory 工厂){_contextFactory = 工厂;}//此方法将使用上下文设置受保护的 Context 属性//由工厂创建公共无效 SetContext(){上下文 = _contextFactory.CreateDbContext();}public void RelatedSuburbEntities(郊区郊区){Context.Entry(suburb).Reference(a => a.State).Load();}}

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

公共类 HomeController : 控制器{私有只读 IClientRepository _repository;公共家庭控制器(IClientRepository 存储库){_repository = 存储库;}公共 IActionResult 索引(){//将 connectionString 保存在 HttpContext.Items 中HttpContext.Items["connection-string"] = "test-connection";//设置上下文_repository.SetContext();返回视图();}}

确保您将 ConfigureServices 中的 IContextFactory 注册为开放泛型和 Singleton,如下所示,同时注册 HttpContextAccessor 和 IClientRepository

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

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天全站免登陆