在基本存储库类的构造函数中配置DBContext

时间:2019-04-26 23:00:46

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

在我的解决方案启动后,我需要实例化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(通过绕过启动时将上下文添加为服务的需要),又要用“ using”等包装每个虚拟方法,

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

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

下面的答案需要修改,因为这个question发现了工厂答案(有前途的)有错误。.恩科西对此错误做出了很好的回答。

编辑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 <>));

2 个答案:

答案 0 :(得分:2)

已编辑

  

已编辑答案,以纠正发现的错误并   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>();

答案 1 :(得分:1)

您可以这样定义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);
    }
}