Внедрение зависимостей динамического DbContext в сервис - PullRequest
0 голосов
/ 11 июня 2018

Я создаю .Net Web Api, который использует шаблон Service / Repository с Entity Framework.Ниже представлен контроллер с действиями 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, поскольку сервер и база данных не известны до тех пор, пока не будет выполнен запрос, и различные данные конфигурации не будут переданы через строку запроса, например? Client = Client & property = Property.Каждый клиент имеет свою собственную базу данных, и каждая база данных расположена на одном из двух серверов.

Существует внутренний пакет NuGet с незащищенной ContextFactory, который может быть вызван для получения соответствующего DbContext при выполнении запроса:

ContextFactory.GetSomeContext(client, prop);

Сначала я подумал об использовании ActionFilter на BaseController для анализа строки запроса HTTPActionContext Request для ключей Client и Property.С помощью этой информации контекст может быть получен и зарегистрирован с помощью контейнера 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());

    ...
}

Этот план не удался, когда я понял, что ActionFilter выполняется после инициализации SomeController, и поэтому его конструктор и конструктор Service ужезавершено выполнение, и уже слишком поздно зарегистрировать DbContext.

Вопрос: Каков наилучший способ извлечь динамический DbContext и использовать Unity для внедрения этого экземпляра в конструктор Service?

I 'мы читали об использовании делегатов, например

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

, а затем в UnityConfig.cs RegisterComponents ()

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

, но я не уверен, как на самом деле предоставить клиента и свойство во время выполненияиз строки запроса.

Спасибо за любую помощь!

1 Ответ

0 голосов
/ 13 июня 2018

Звучит так, как будто вы хотите использовать мультитенантную структуру «база данных на арендатора».Во-первых, следует избегать передачи идентификаторов клиентов (ClientID) в строку запроса.Вместо этого я бы порекомендовал прочитать OAuth и Owin, чтобы встроить идентификацию клиента (и даже строку подключения) в токен аутентификации.Я использую структуру Db-per-tenant в своих приложениях, которая работает примерно следующим образом:

  1. Пользователь посещает сайт и направляется на вход.

  2. Служба аутентификации взаимодействует с центральной БД, управляющей всеми арендаторами.Эта БД служит концентратором для управления тем, какой сервер / база данных хранится у этого арендатора, и их версией БД.(Направлено на серверы приложений с поддержкой версий во время переходов обновления)

  3. При успешной аутентификации токен OAuth загружается с идентификатором клиента, именем схемы (имя БД) и строкой соединения.Он хранится в зашифрованном виде.

  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 использует делегированный подход, когдапостроение DbContexts, которые извлекают стратегию идентификации арендатора (часть 3) и получают строку соединения для предоставления контексту.Стратегия может кэшировать сведения об арендаторе для нескольких вызовов при разрешении DbContexts, ее область действия должна быть для каждого запроса.

Я обычно использую шаблон работы блока DbContextScope и адаптировал ветвьон подходит для мультитенантных сред для сред дБ-на-арендатора или схемы-на-арендатора.Не стесняйтесь взглянуть @ https://github.com/StevePy/DbContextScope

...