Создать EF Core DbContext во время выполнения на основе параметров запроса - PullRequest
0 голосов
/ 19 февраля 2019

Справочная информация

Мы создаем веб-приложение с использованием ASP.Net Core и Entity Framework Core 2.2

Мы подключаемся к устаревшим базам данных.Установка состоит в том, что существует 16 баз данных с одинаковой схемой, содержащих разные контекстные данные.Мы не можем изменить это.

У нас есть требование подключаться к конкретной базе данных во время выполнения, основываясь на параметрах запроса.

Представьте, что для каждой компании под материнской компанией существует база данных.

Представьте, что в каждой базе данных есть таблицы, такие как Employee, Client и Shift (смена, которую сотрудник работал для клиента).

Существует также «центральная» база данных, которая содержит информацию, общую для всехдругие базы данных, такие как настройки и т. д.

У нас есть требование перечислить всех сотрудников всех компаний в одном представлении списка.

Мы планируем получить эти данные с помощью представления SQL вцентральная база данных, которая просто объединяет все остальные базы данных (если у вас есть предложения о том, как сделать это лучше, поделитесь ею).

CREATE VIEW dbo.v_all_employees AS 
    SELECT EmployeeId, Fullname, 1 AS BusinessId FROM BusinessA.dbo.Employees
    UNION ALL SELECT EmployeeId, Fullname, 2 AS BusinessId FROM BusinessB.dbo.Employees
    -- etc. etc.

У нас есть один набор моделей, которыепредставляют все сущности (таблицы) во всех базах данных, поскольку они используют одну и ту же схему, например, один класс Employee, один клиенткласс и т. д.

Вариант использования

Пользователь переходит на веб-страницу, чтобы просмотреть список сотрудников по всем предприятиям по этому маршруту:

http://example.org/employees

Затем пользователь щелкает ссылку «Сведения» для отдельного сотрудника, чтобы просмотреть дополнительные сведения, переводя пользователя на URL:

http://example.org/employees/details?employeeId=123&businessId=xyz

Я застрял в том, как нам создать экземпляр DbContext для конкретного бизнеса во время выполнения, учитывая BusinessId.

Я придумал три способа (и теперь четвертый благодаря @mylee) для достижения желаемогорезультат и я жду отзывов от сообщества.

Каждая опция предполагает, что каждый DbContext будет реализовывать интерфейс, который предоставляет все методы DbSets и DbContext, а затем мы будем использовать шаблон Factory для обработки определения того, какой DbContextреализация для использования.

Первый вариант: Просто заставьте Factory создать правильный DbContext на основе параметра запроса 'bizId'.

Однако для этого требуется, чтобы каждый DbContext перезаписывал метод OnConfiguring и устанавливал DbProvider - то, что ядро ​​dotnet Core делает для нас через метод расширения контейнера IoC AddDbContext :

public class ContextFactory : IContextFactory
{
    public IBIZContext GetContext(int bizId)
    {
        switch (bizId)
        {
            // Newing-up the DbContexts like this requires that the OnConfiguring method
            // for each context be present in each DbContext to setup the DbProvider
            // with the correct connection string.
            case 6:
                return new BIZ_AContext();
            case 7:
                return new BIZ_BContext();
            default:
                throw new Exception("Unexpected Business Id");
        }
    }
}

Проблема в том, что мне не нравится, как мы обновляем контексты здесь.Это требует, чтобы мы переопределяли метод OnConfiguring в каждом контексте и имели доступ к строке подключения.

Второй вариант:

Я бы предпочел использовать встроенный контейнер IoC, который настроен в Startup.cs, но при этом отображается службаЛокатор Анти-Узор.Кроме того, он пропускает HttpContext из веб-проекта в проект инфраструктуры (я использую архитектуру Onion):

public class ContextFactoryUsingLocator : IContextFactoryUsingLocator
{
    public IBIZContext GetContext(IHttpContextAccessor httpContextFactory, int bizId)
    {
        // Injecting the HttpContextAccessor gives us access to the IoC Container via RequestServices;
        // But using it here exhibits the Service Locator anti-pattern.
        // Perhaps its ok to use the Service Locator pattern within a Factory in this case?
        switch (bizId)
        {
            case 6:
                return (BIZ_AContext)httpContextFactory.HttpContext.RequestServices.GetService(typeof(BIZ_AContext));
            case 7:
                return (BIZ_BContext)httpContextFactory.HttpContext.RequestServices.GetService(typeof(BIZ_BContext));
            default:
                throw new Exception("Unexpected Business Id");
        }
    }
}

Третий вариант

Внедрить каждый DbContext в Factory и заставить Factory просто вернуть правильный экземпляр:

public class ContextFactoryInjected : IContextFactoryInjected
{
    private readonly BIZ_AContext _bizAContext;
    private readonly BIZ_BContext _bizBContext;

    public ContextFactoryInjected(
        BIZ_AContext bizAContext, 
        // 14 other DbContext dependencies omitted here for brevity
        BIZ_BContext bizBContext)
    {
        // Injecting all 16 DbContexts into the Factory seems to counter the intention of the Factory since the IoC Container
        // would be managing the creation of all the instances; isn't that the responsibility of the Factory?

        // More importantly; wouldn't this have serious performance implications, creating 16 instances of a DbContext on every Request?
        _bizAContext = bizAContext;
        _bizBContext = bizBContext;
    }

    public IBIZContext GetContext(int bizId)
    {
        switch (bizId)
        {
            case 6:
                return _bizAContext;
            case 7:
                return _bizBContext;
            default:
                throw new Exception("Unexpected Business Id");
        }
    }
}

Четвертый вариант Инкапсулировать конфигурациюDbContext внутри Фабрики (этот метод был предложен @mylee)

public class ContextFactoryConfigured : IContextFactoryConfigured
{
    public IBIZContext GetContext(int bizId)
    {
        switch (bizId)
        {
            // Newing-up the DbContexts like this encapsulates all the details required for the DbContext within the Factory
            case 6:
                var bizAOptionsBuilder = new DbContextOptionsBuilder<BizAContext>();
                bizAOptionsBuilder.UseSqlServer(Settings.BizAConnectionString);
                return new BizAContext(bizAOptionsBuilder.Options);
            case 7:
                var bizBOptionsBuilder = new DbContextOptionsBuilder<BizBContext>();
                bizBOptionsBuilder.UseSqlServer(Settings.BizBConnectionString);
                return new BizBContext(bizBOptionsBuilder.Options);
            default:
                throw new Exception("Unexpected Business Id");
        }
    }
}

Согласны ли вы с тем, что вариант 2 демонстрирует анти-паттерн Service Locator, т. Е. Правильно ли говорить, что Фабрика зависит от объектов, которыми она управляетсоздание?

Считаете ли вы, что вариант 4 является лучшим подходом из них, учитывая, что обычно фабрика несет ответственность за «обновление» своих объектов И это не приводит ксмешивание проблем (т. е. не требует HttpContext) И он инкапсулирует все детали (например, ConnectionString), необходимые для построения контекста внутри фабрики?

Или есть способ добиться этого с помощью Dependency Injection, не приводя к смешению проблем?

Или есть еще лучший способ, о котором я здесь не упоминал?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...