使用依赖注入和接口登录的每个用户的动态DbContext

时间:2018-09-26 18:13:01

标签: .net entity-framework dynamic dependency-injection repository-pattern

我正在构建一个多租户Web应用程序(API),该应用程序将根据登录的用户访问不同的数据库。将有一个数据库来管理用户登录,其中包含构建所需的服务器和数据库信息连接字符串代码端为登录的特定用户构建DbContext。这是从最低级别到控制器的连接:

GenericRepository:

public class GenericRepository<TEntity> : GenericRepositoryBase<TEntity> where TEntity : class
{
    public GenericRepository(DbContext context) : base(context)
    { 
        //GenericRepositoryBase has the CRUD operations
    }
}

IRepository:

public interface IRepository<TEntity> where TEntity : class
{
    IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "");

    Task<TEntity> GetByID(object id);

    Task<ContextReturn> Insert(TEntity entity);

    Task<ContextReturn> Update(TEntity entityToDelete);

    Task<ContextReturn> Remove(TEntity entityToUpdate);
}

特定模型存储库:

public class EmployeeRepository : GenericRepository<Employee>, IEmployeeRepository
{
    public EmployeeRepository(DbContext context) : base(context)
    {

    }
}
API中的

Startup.cs包含:

services.AddScoped<IEmployeeRepository, EmployeeRepository>();

最后是控制器连接:

public class EmployeeController : ControllerBase
{
    IEmployeeRepository EmployeeRepository { get; }

    public EmployeeController(
        IEmployeeRepository employeeRepository)
    {
        EmployeeRepository = employeeRepository;
    }

    public IActionResult GetEmployees()
    {
        var data = EmployeeRepository.GetAll().ToList();

        return Ok(data);
    }
}

所有这些在启动时声明为以下单个DbContext时都可以正常工作:

services.AddDbContextPool<DbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SystemManagementConnection"), sqlServerOptions => sqlServerOptions.CommandTimeout(300)));

(实际上,我正在使用正确命名的确切DI上下文来访问用户管理数据库)。

由于以下两个原因,试图根据用户登录动态创建DbContext时会出现问题:

  1. 以这种方式使用DI时,您不会在 控制器,并在我使用接口时将其传递到存储库中 访问它们。 DI使用启动中声明的DbContext来 通过,我不需要所有存储库。

  2. Startup.cs不了解任何用户信息,因此不能 通过任何一种方法来构建基于 该信息。

我尝试使用以下方法解决第一个问题:

services.AddScoped<IEmployeeRepository, EmployeeRepository>((ctx) => { DbContext context = DbContextFactory.Create(); return new EmployeeRepository(context); });

附加到:

public class DbContextFactory
{
    public static DbContext Create()
    {
        var conn = GetConnectionString();

        if (!string.IsNullOrEmpty(conn))
        {
            var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
            optionsBuilder.UseSqlServer(conn);
            return new DbContext(optionsBuilder.Options);
        }
        else
        {
            throw new ArgumentNullException("ConnectionId");
        }
    }

    public static string GetConnectionString()
    {
        var data = "";

        return data;
    }
}

,然后将IHttpContextAccessor传递到构造函数中以访问用户(我确定您已经知道它的去向...),但是当然,这要求该类不是静态的,然后在启动中引发错误。 CS文件。本质上,这不能解决我的问题。

我唯一想到的另一种解决方案是将DbContext的 ALL 声明到Startup.cs文件的服务中,但是同样,我的存储库仍然不知道要使用哪个存储库。用户。

我当然不是第一个遇到此问题的人。如果有人可以提供帮助,我将不胜感激。

1 个答案:

答案 0 :(得分:1)

这是基于发布的来源的分步解决方案

  1. IHttpContextAccessor注册为单例服务。
  2. 修改DbContextFactory类并注册为单例服务,现在您可以从类的构造函数中请求IHttpContextAccessor服务了;然后使用它来动态创建连接字符串。
  3. 使用Scoped模式添加每个存储库实现。并修改实现工厂功能以加载DbContextFactory服务。

以下是一个实现示例:

DbContextFactory类:

public class DbContextFactory
{
    private readonly IHttpContextAccessor _httpContext;

    public DbContextFactory(IHttpContextAccessor httpContext)
    {
        _httpContext = httpContext;
    }

    public DbContext Create()
    {
        var conn = GetConnectionString();

        var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
        optionsBuilder.UseSqlServer(conn);
        return new DbContext(optionsBuilder.Options);
    }

    private string GetConnectionString()
    {
        //--use the `_httpContext` to build the connection string dynamic
        var user = _httpContext?.HttpContext?.User;
        //...
        return "TODO";
    }
}

ConfigureServices类的Startup方法的部分修改:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddSingleton<DbContextFactory>();
    services.AddScoped<IEmployeeRepository, EmployeeRepository>(
        (servicesProvider) =>
        {
            var contextFactory = servicesProvider.GetService<DbContextFactory>();
            DbContext context = contextFactory.Create();
            return new EmployeeRepository(context);
        });
    ...
}

希望对您有帮助!