动态DbContext依赖注入服务

时间:2018-06-11 18:18:59

标签: .net entity-framework asp.net-web-api dependency-injection unity-container

我正在构建一个使用服务/存储库模式w / Entity Framework的.Net Web Api。下面是一个控制器,其中包含与服务接口的CRUD操作:

public class SomeController : BaseApiController
{
    private IService _service;
    public SomeController(IService _service)
    {
        _service = service;
    }

    public object Get() { return _service.GetItems(); }
    ...
}

我想使用Microsoft Unity IoC将数据库上下文注入到下面的服务构造函数中:

// Service implements IService
public Service(SomeContext ctx) : base(ctx)
{
    _alpha = new AlphaRepository(ctx);
    _bravo = new BravoRepository(ctx);
}

这对我来说非常适用于单个静态DbContext。但是,Api必须使用动态DbContext b / c,在发出请求并且通过查询字符串传递各种配置数据之前,服务器和数据库是未知的。 ?客户=客户端和放大器;属性=属性。每个客户端都有自己的数据库,每个数据库都位于两个服务器之一。

有一个内部开发的NuGet包,带有暴露的ContextFactory,在发出请求时可以调用它来检索适当的DbContext:

ContextFactory.GetSomeContext(client, prop);

首先,我想在BaseController上使用ActionFilter来解析客户端和属性键的HTTPActionContext请求的查询字符串。使用此信息,可以检索上下文并使用Unity容器注册:

// Belongs to 'public class SomeFilterAttribute : ActionFilterAttribute'
public override void OnActionExecuting(HttpActionContext actionContext)
{
    var someContext = ContextFactory.GetSomeContext(client, prop);

    private IUnityContainer _unityContainer;
    _unityContainer = (IUnityContainer)actionContext.Request.GetDependencyScope().GetService(typeof(IUnityContainer));
    _unityContainer.RegisterInstance<SomeContext>(someContext, new PerThreadLifetimeManager());

    ...
}

当我意识到在SomeController初始化之后执行ActionFilter并且因此它的构造函数和Service构造函数已经完成执行并且注册DbContext为时已晚时,这个计划失败了。

问题: 检索动态DbContext并使用Unity将此实例注入Service构造函数的最佳方法是什么?

我读过有关使用委托的内容,例如

public delegate ISomeContext CreateSomeContext(string client, string prop);

然后在UnityConfig.cs RegisterComponents()

container.RegisterInstance<CreateSomeContext>((c, p) => ContextFactory.GetSomeContext(c, p));

但我不确定如何在运行时从查询字符串中实际提供客户端和属性。

感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

这听起来像是要使用每个租户数据库的多租户结构。首先要避免在查询字符串上传递租户标识符(ClientID)。相反,我建议您阅读OAuth和Owin,以在身份验证令牌中嵌入客户端标识(甚至是连接字符串)。我在我的应用程序中使用Db-per-tenant结构,大致如下:

  1. 用户访问网站并定向登录。

  2. 身份验证服务与管理所有租户的中央数据库进行通信。此DB充当集线器,用于控制存储租户的服务器/数据库及其数据库版本。 (在升级过渡期间定向到版本化的应用服务器)

  3. 成功验证后,OAuth令牌将加载租户ID,架构名称(DB名称)和连接字符串。它存储为加密的声明。

  4. 数据服务器请求使用策略模式检索Owin会话上下文以检索令牌,并根据身份验证服务对其进行验证。一个例子:

    TenantConnectionModel ITenantIdentityStrategy.RetrieveTenant()
    {
        if (_tenant != null) // Cached copy...
            return _tenant;
    
        var context = HttpContext.Current.GetOwinContext();
        var eMailAddress = context.Authentication.User.Identity.Name;
    
        var tenantIdClaim = context.Authentication.User.Claims.SingleOrDefault(x => x.Type == "YourNamespace.TenantId");
        var schemaClaim = context.Authentication.User.Claims.SingleOrDefault(x => x.Type == "YourNamespace.DBName");
        var connectionStringClaim = context.Authentication.User.Claims.SingleOrDefault(x => x.Type == "YourNamespace.ConnectionString");
    
        if (tenantIdClaim == null || schemaClaim == null || connectionStringClaim == null)
            return null;
    
        // TODO example: Call the auth service here with the e-mail address and tenant ID
        // to validate that the current user has logged in and session hasn't timed
        // out. 
    
        _tenant = new TenantConnectionModel
        {
            TenantId = long.Parse(tenantIdClaim.Value),
            SchemaName = schemaClaim.Value,
            ConnectionString = connectionStringClaim.Value,
        };
    
        return _tenant;
    }
    
  5. 成功验证后,它会提取租户详细信息,包括连接字符串。

  6. IOC Container在构造DbContexts时使用委托方法,DbContexts检索租户标识策略(第3部分)并获取要提供给上下文的连接字符串。该策略可以在解析DbContexts时缓存多个调用的租户详细信息,它的生命周期范围应该是每个请求。

  7. 我通常使用DbContextScope工作模式单元,并对其进行了调整,以适应每个租户或每个租户架构环境的多租户环境。随意看看@ https://github.com/StevePy/DbContextScope