Внедрить строку пользовательского подключения в 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)
{
}
}