Для моих систем у меня обычно есть три базы данных: разработка, тестирование и производство.
Я делаю это с помощью одного абстрактного базового класса, который наследуется от DbContext
и принимает строку подключения в своем конструкторе. Затем у меня есть три конкретных класса, которые наследуются от этого базового класса, каждый из которых передает другую строку подключения (и с конструктором по умолчанию, чтобы миграции могли его вызывать).
public abstract class MyBaseContext(
string connectionString
) : DbContext {
...
}
public sealed class MyProductionContext()
: MyBaseContext(productionConnectionString);
public sealed class MyDevelopmentContext()
: MyBaseContext(developmentConnectionString);
public sealed class MyTestingContext()
: MyBaseContext(testingConnectionString);
Затем я могу использовать что-то вроде этого:
dotnet ef migrations add AddSomeNewFields --context MyDevelopmentContext
dotnet ef database update --context MyDevelopmentContext
добавить новую миграцию с некоторыми новыми изменениями и применить ее к конкретной базе данных.
Проблема в том, что в итоге у меня получается три набора миграций для трех баз данных.
Есть ли способ создать только один набор миграций и применить этот набор миграций к нескольким экземплярам базы данных?
@LukeVo, спасибо, но это уже мои настройки
MyDevelopmentContext
твой базовый класс? Мне было интересно, можно ли использовать базовый класс в качестве DbContext миграции, и ваш фактический DbContext можно будет обновить из него?
@LukeVo ну, если бы это не было абстрактно, то я мог бы использовать базу. но тогда он не будет нацелен на какую-либо конкретную базу данных. Мне нужно было бы как-то передать ему строку подключения. Думаю, да, мне нужно использовать тот же контекст, но нацелиться на другую БД. Есть ли способ сделать это?
Я не понимаю, зачем вам три контекста, просто передайте строку подключения с помощью сценария dotnet?
Да, я думал, тебе нужен какой-то конкретный DI для каждого окружения. В этом случае, как и в другом комментарии, почему бы вам вместо этого просто не настроить строку подключения с помощью одного класса DbContext? Черт возьми, вы могли бы даже использовать appsettings.Development.json
и т. д., если не хотите использовать сценарий развертывания.
@YehorAndrosov, ок, как мне это сделать? Я не вижу варианта --connectionstring
с dotnet ef migrations add
. И могу ли я откуда-то вытащить значение или мне придется каждый раз делать это «вручную» (хотя, вероятно, в файле .bat)?
Это помогает? Learn.microsoft.com/en-us/aspnet/core/fundamentals/…
@LukeVo, похоже, речь идет о конфигурации из кода C#, которую я уже могу сделать; но как мне получить значения конфигурации из командной строки и передать их dotnet ef
? (Звучит так, будто мне следует использовать переменные среды?)
Я считаю, что для этого можно использовать фабрику: Learn.microsoft.com/en-us/ef/core/cli/…. Что касается обновления, я обычно помещаю эту логику в код запуска во время выполнения, но вы также можете сделать это во время развертывания с помощью профиля публикации.
@LukeVo это кажется многообещающим, спасибо
То, что я сделал (как намекают комментарии), реализовано из IDesignTimeDbContextFactory<TContext>
.
Это позволяет использовать произвольные аргументы командной строки для построения вашего контекста без необходимости иметь разные типы контекста. Откуда вы получите значения конфигурации, все еще зависит от вас, но это обеспечивает механизм для изменения вашего контекста из командной строки любым удобным для вас способом.
Я сократил свой контекст примерно до этого:
public sealed class MyContext(
string connectionString
) : DbContext {
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder
) =>
optionsBuilder
.Use...(connectionString);
...
}
А затем реализовали фабрику контекста примерно так:
public sealed class MyContextFactory : IDesignTimeDbContextFactory<MyContext> {
public MyContext CreateDbContext(
string[] args
) {
if (args.Length != 1)
throw new InvalidOperationException(...);
var arg = args[0].ToLower();
var cs = arg switch {
"production" => productionConnectionString,
"development" => developmentConnectionString,
"testing" => testingConnectionString,
_ => throw new InvalidOperationException(...)
};
return new MyContext(cs);
}
// these are for use in code
public MyContext CreateProduction() => CreateDbContext("production");
public MyContext CreateDevelopment() => CreateDbContext("development");
public MyContext CreateTesting() => CreateDbContext("testing");
}
Затем вы можете сделать следующее из командной строки (синтаксис --
приводит к тому, что остальные аргументы командной строки передаются как есть в ваш фабричный метод):
dotnet ef migrations add SomeNewMigration -- development
dotnet ef database update -- development
dotnet ef database update -- production
dotnet ef database update -- testing
и при этом будет использоваться одна миграция для всех трех подключений к базе данных.
Пример использования appsettings.json
может выглядеть так:
public sealed class MyContextFactory : IDesignTimeDbContextFactory<MyContext> {
public MyContext CreateDbContext(
string[] args
) {
if (args.Length != 1)
throw new InvalidOperationException(...);
var name = args[0];
// you can use arguments to pull from a different settings file
// or you can put full cnxn strings in the file under a
// "ConnectionStrings" grouping and use .GetConnectionString()
// to find them
// or you can put specific values like paths and build the rest
// of the cs here
// index like: ["Key:Value"]
// lots of options
var cs =
new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build()
.GetConnectionString(name); // the arg you pass finds the cs by name
if (cs == null)
throw new InvalidOperationException(...);
return new MyContext(cs);
}
// these are for use in code when you need a particular connection
// ie: testing for testing
public MyContext CreateProduction() => CreateDbContext("production");
public MyContext CreateDevelopment() => CreateDbContext("development");
public MyContext CreateTesting() => CreateDbContext("testing");
}
appsettings.json
пример:
{
"ConnectionStrings": {
"Production": "...",
"Development": "...",
"Testing": "..."
}
}
Хотя это выглядит хорошо, я думаю, что стандартным способом следует использовать конструктор DbContextOptions<MyContext>
(как в базовом классе DbContext
), а не просто строку подключения, например вот эту. Рад, что это сработало для вас, хотя
Вам не нужно передавать среду для вызова dotnet ef migrations add
— для сравнения используется локальная схема моментального снимка и текущий контекст базы данных.
dotnet ef database update
, с другой стороны, сравнивает миграции в проекте с теми, которые хранятся в таблице истории миграции. Если строка подключения хранится в файле appsettings.{Environment}.json
, вы можете использовать
dotnet ef database update -- --environment Production
Он прочитает строку подключения из appsettings.Production.json
Если вы не храните учетные данные в файлах конфигурации, вы можете использовать опцию --connection
.
dotnet ef database update 20180904195021_InitialCreate --connection your_connection_string
Кажется, это не совсем работает, как вы говорите. Мне все еще нужно указать среду при добавлении, и мне все еще нужно реализовать IDesignTimeDbContext<T>
, чтобы использовать значение среды для выбора строки подключения для передачи в мой контекст. разве мой контекст не должен быть настроен определенным образом? обратите внимание, что это не имеет ничего общего с ASP.NET. но переменная DOTNET_ENVIRONMENT
пуста, если при добавлении я не указываю значение среды.
ну, насколько я могу судить, это вообще не работает. Я думаю, что требуется гораздо больше настроек, о которых вы здесь не упомянули.
вернусь к компьютеру и подготовлю демо
Спасибо, у меня с IDesignTimeDbContextFactory<T>
это работает вполне хорошо. Чтобы эти другие вещи работали, требуется, чтобы какой-то другой класс анализировал ваши аргументы за вас. Но вы также можете просто обработать их самостоятельно с помощью метода CreateDbContext
, а затем обрабатывать их так, как захотите. Хотя спасибо за ответы.
да, я пропустил ту часть, где речь шла о ядре .net :-) создание пустого основного объекта asp.net только для миграции также является вариантом
Работает ли это, если у вас есть абстрактный класс в качестве общего базового класса для всех трёх
DbContext
? Просто предложение, я не уверен, сработает ли оно.