【问题标题】:Configuring DBContext in the constructor of my base repository class在我的基础存储库类的构造函数中配置 DBContext
【发布时间】:2019-09-16 10:52:46
【问题描述】:

我有一种情况,我需要在我的解决方案启动后实例化我的 DBContext。我问过这个question,这表明我可以用constructor argument 做到这一点。

有人建议我作为一个例子来实现:

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(通过绕过在启动时将上下文添加为服务的需要)以及使用“使用”等包装每个虚拟方法来实现此更改

EDIT.. Camilo 表示我没有确定我何时拥有数据库名称。

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

下面的答案需要修改,因为带有工厂答案(有希望)的答案有这个question发现的错误。Nkosi 对错误给出了很好的答案。

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

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

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

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

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

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

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

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

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

【问题讨论】:

  • “在解决方案运行启动之前没有连接字符串”但是你什么时候有连接字符串?用户给你连接字符串了吗?

标签: c# asp.net-core dependency-injection entity-framework-core


【解决方案1】:

已编辑

已编辑答案以纠正发现的错误和 fixed 来自 Nkosi。谢谢,@Nkosi。

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

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

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

创建一个实现此接口的工厂类(根据 Nkosi answer 编辑)。 进一步修改以注入 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);
    }
}

然后修改您的基础存储库并使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>();

【讨论】:

  • 这看起来不错..我可以澄清一些事情,因为我因为休息而生锈了..我怎么称呼我的客户端存储库类,它有这个作为它的基类,因为我有现在提供一个连接字符串..我刚刚将客户端存储库注入到服务类中,我现在可以(必须)提供一个连接字符串..
  • 感谢您的回答。所以从这个问题中不清楚你从哪里得到这个connectionString?我从你的问题中猜到的是,在用户是 sigendIn 之后,你就有了你需要连接到的数据库的 connectionString。因此,当您在服务的构造函数中注入客户端存储库时,您可以设置基类的 ConnectionString 属性。如果您的 ConnectionString 是静态的,您可以从 JobsLedgerAPIContext 中的配置中读取它,或者按照@Mason Zhang 提到的那样创建您的上下文。
  • 已尝试实施此操作并收到错误.. 写了另一个问题stackoverflow.com/questions/55886430/… 并且 cmets 中的答案表明代码无效.. 你能再看看我还注意到一个分号在 IContextFactory 之后也不应该在那里.. 谢谢
  • 我想知道您是否可以更新您的问题以反映 Nkosi 的回应,他亲切地指出了如何解决该错误。您能否扩展以包括此注册以及如果可能的话如何使用..
  • @si2030 请查看编辑后的答案。我猜这个解决方案有点啰嗦,需要对现有类进行一些更改。但是,我认为没有一种简单的方法可以解决您面临的问题。请让我知道这是否适合您。
【解决方案2】:

您可以像这样定义您的 JobsLedgerAPIContext:

public class JobsLedgerAPIContext : DbContext
{
    // public DbSet<Job> Jobs { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=dotnetcore;");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // may need to reflect entity classes and register them here.

        base.OnModelCreating(modelBuilder);
    }
}

【讨论】:

    猜你喜欢
    • 2016-12-13
    • 2016-03-12
    • 1970-01-01
    • 1970-01-01
    • 2022-12-03
    • 2018-04-29
    • 1970-01-01
    • 1970-01-01
    • 2018-03-31
    相关资源
    最近更新 更多