Внедрить строку пользовательского подключения в Entity Framework DbContext

Я хочу добавить пользовательскую строку подключения в свой контекст EF вместо использования строки подключения в моем web.config. Идея состоит в том, чтобы переместить всю логику, связанную с базой данных, из моего проекта MVC на отдельный слой. Я также хочу, чтобы этот слой отвечал за правильные строки подключения вместо моих веб-приложений.

В настоящее время службы, использующие контекст, вызывают конструктор по умолчанию:

using (var context = new MyDbContext()) {
    //...
}

Конструктор по умолчанию внутренне вызывает DbContext с именем строки подключения из web.config:

public partial class MyDbContext : DbContext
{
    public MyDbContext()
        : base("name=MyDbContext")
    {
    }

    //...
}

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

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

Так как MyDbContext является частичным классом, можно добавить дополнительный конструктор в отдельный файл класса partial class MyDbContext, но это также кажется вонючим. Я не знаю, почему, но мой мозг говорит плохую идею.

После некоторого расследования я выяснил, что EF может включить этот дополнительный конструктор, отредактировав шаблон T4 Model.Context.tt, который представляет собой сочетание С# и некоторой разметки шаблона. Вот оригинальный конструктор:

public <#=Code.Escape(container)#>()
    : base("name=<#=container.Name#>")
{
<#
    WriteLazyLoadingEnabled(container);
#>
}

Очевидно, легко добавить аналогичную логику для создания перегруженного конструктора, содержащего строку подключения:

public <#=Code.Escape(container)#>(string nameOrConnectionString)
    : base(nameOrConnectionString)
{
<#
    WriteLazyLoadingEnabled(container);
#>
}

Я попробовал это и заметил, что как повторное создание классов моделей, так и обновление модели из БД не повлияют на шаблон T4, поэтому всегда будет существовать дополнительный конструктор. Хорошо! На первый взгляд это выглядит как подходящее решение, но...

И вот мой вопрос: Это действительно хорошее решение?

Снова сравните три варианта:

  • Отредактируйте автоматически сгенерированный класс (хорошо, мы договорились забыть об этом)
  • Добавить конструктор в файл частичного класса
  • Отредактируйте шаблон T4, чтобы сообщить EF о создании дополнительного конструктора

Из этих трех вариантов третий кажется мне самым удобным и чистым решением. Каково твое мнение? У кого-то есть веская причина, почему это было бы плохой идеей? Есть ли дополнительные возможности для ввода строк подключения, возможно, с помощью пользовательского connectionFactory? Если да, то как мне это сделать?

Ответ 1

Из 3 вариантов, которые вы предоставляете, я бы выбрал вариант 2. Не было ли введено неполное ключевое слово для решения проблемы с автогенерируемыми файлами? Использование шаблонов T4 приносит дополнительную сложность, на мой взгляд, у поддерживающего разработчика есть чем понять еще одну вещь (не все знакомы с T4), но расширение частичного класса является более стандартной разработкой С#.

Однако я не знаю ответа на вопрос "есть ли действительно хорошее решение". Представляя factory, он снова вводит новый код для поддержки, тестирования и понимания, я не уверен, что эта абстракция помогает здесь, потому что factory необходимо будет создать экземпляр dbcontext с соответствующим конструктором (который вы все равно должны предоставить).

Edit:

Просто подумал об этом: factory может быть полезен для разрешения строки подключения из другого места (вы сказали, что не хотите, чтобы строка подключения в web.config), но это все еще не решает проблему проблема конструктора

Ответ 2

Как @shaft , я заменил свой подход к шаблону T4 на использование частичного класса. Как оказалось, это действительно намного проще и интуитивно, чем любое другое решение, о котором я знаю.

Сгенерированный автоматически класс выглядит следующим образом:

public partial class MyDbContext : DbContext
{
    public MyDbContext() : base("name=MyDbContext")
    {
    }

    //...
}

Я просто добавил еще один неполный класс с тем же именем. Проблема решена.

public partial class MyDbContext 
{
    public MyDbContext(string nameOrConnectionString)
             : base(nameOrConnectionString) 
    {
    }
}