Wie und wo soll Database.EnsureCreated und Database.Migrate aufgerufen werden?

78

Ich habe eine ASP.NET MVC 6-Anwendung und muss die Methoden Database.EnsureCreatedund aufrufen Database.Migrate.

Aber wo soll ich sie anrufen?

Bailando Bailando
quelle
Möglicherweise möchten Sie auch nicht verwenden. MS-Dokumente sagen dies über die Verwendung von Migrate (): "Während es sich hervorragend für Apps mit einer lokalen Datenbank eignet, erfordern die meisten Anwendungen eine robustere Bereitstellungsstrategie wie das Generieren von SQL-Skripten." docs.microsoft.com/en-us/ef/core/managing-schemas/migrations
John Pankowicz

Antworten:

94

Ich denke, das ist eine wichtige Frage und sollte gut beantwortet werden!

Was ist Database.EnsureCreated?

context.Database.EnsureCreated()ist eine neue EF-Kernmethode, die sicherstellt, dass die Datenbank für den Kontext vorhanden ist. Wenn es existiert, wird keine Aktion ausgeführt. Wenn es nicht vorhanden ist, werden die Datenbank und ihr gesamtes Schema erstellt und es wird sichergestellt, dass es mit dem Modell für diesen Kontext kompatibel ist.

Hinweis: Diese Methode verwendet keine Migrationen zum Erstellen der Datenbank. Darüber hinaus kann die erstellte Datenbank später nicht mithilfe von Migrationen aktualisiert werden. Wenn Sie auf eine relationale Datenbank abzielen und Migrationen verwenden, können Sie mithilfe der DbContext.Database.Migrate()Methode sicherstellen, dass die Datenbank erstellt und alle Migrationen angewendet werden.

Wie haben wir das mit EF 6 gemacht?

context.Database.EnsureCreated() entspricht den unten aufgeführten Ansätzen von EF 6:

  1. Paketmanager-Konsole:

    Enable-Migrations -EnableAutomaticMigrations. Add-Migration / Update-Datenbank.

  2. Aus dem Code:

    Database.SetInitializer CreateDatabaseIfNotExists

oder

Mit DbMigrationsConfiguration und setzen Sie AutomaticMigrationsEnabled = true;

Was ist Database.Migrate?

Wendet alle ausstehenden Migrationen für den Kontext auf die Datenbank an. Erstellt die Datenbank, falls sie noch nicht vorhanden ist.

Wie haben wir das mit EF 6 gemacht?

context.Database.Migrate() entspricht den unten aufgeführten Ansätzen von EF 6:

  1. Paketmanager-Konsole:

    Update-Database -TargetMigration

  2. Mit einer benutzerdefinierten DbMigrationsConfiguration:

    AutomaticMigrationsEnabled = false; oder mit DbMigrator.

Fazit :

Wenn Sie Migrationen verwenden, gibt es context.Database.Migrate(). Wenn Sie keine Migrationen und nur eine schnelle Datenbank (normalerweise zum Testen) möchten, verwenden Sie context.Database.EnsureCreated () / EnsureDeleted ().

Bassam Alugili
quelle
2
Hallo Bassam Alugili, danke für deine Antwort! In meinem Projekt verwende ich Migrationen. Ich wusste nicht, dass man nicht beide Methoden zusammen verwenden sollte.
bailando bailando
1
uw und hier ist ein Beispiel, wie man es nennt! stefanhendriks.com/2016/04/29/…
Bassam Alugili
1
Ich dachte, dass Database.Migrate()Migrationen (falls erforderlich) erstellt werden und dann die darauf basierende Basis aktualisiert wird. Ähnlich wie bei der automatischen Migration in EF 6. Aber ich habe mich geirrt. Es werden nur vorhandene Migrationen (falls vorhanden) in der Datenbank angewendet.
Afshar Mohebi
Soweit ich weiß, verwendet Database.Migrate dieselben Datenbank-Anmeldeinformationen, die von der App beim Einfügen / Abfragen usw. für die Datenbank verwendet werden. Möchten wir, dass diese Aktionen von einem Benutzer mit Berechtigungen zum Erstellen / Löschen ausgeführt werden? Gibt es eine Möglichkeit, Database.Migrate () andere Anmeldeinformationen verwenden zu lassen (mit Berechtigungen zum Erstellen / Löschen)?
Ásgeir Gunnar Stefánsson
2
Du hast mich gerade vor einer zukünftigen Katastrophe gerettet. Kudos
Shaswat Rungta
24

Mit den Informationen, die James P und Bassam Alugili zur Verfügung stellten, fügte ich der ConfigureMethode in der StartupKlasse ( Startup.cs ) diese Codezeilen hinzu :

using (var scope = 
  app.ApplicationServices.CreateScope())
using (var context = scope.ServiceProvider.GetService<MyDbContext>())
    context.Database.Migrate();
Bailando Bailando
quelle
Genau das habe ich gesucht. Die meisten Beispiele verwenden entweder .Net Core oder Web und ich war in einer Windows Forms-Anwendung mit .Net 4.6. Die Datenbank wurde bereits erstellt (da der Benutzer in der Verbindungszeichenfolge keine Rechte zum Erstellen von Datenbanken hat). Und der obige Code hat alle Tabellen und alles aus den Migrationen erstellt.
nivs1978
16

Nur als Vorwort sollten Sie dies von Rowan Miller lesen :

... EnsureCreatedumgeht Migrationen vollständig und erstellt nur das Schema für Sie. Sie können dies nicht mit Migrationen mischen. EnsureCreatedwurde für Tests oder Rapid Prototyping entwickelt, bei denen Sie die Datenbank jedes Mal löschen und neu erstellen können. Wenn Sie Migrationen verwenden und diese beim Start der App automatisch anwenden möchten, können Sie sie context.Database.Migrate()stattdessen verwenden.

Nach beantworten hier müssen Sie fügen Globals.EnsureDatabaseCreated();es zuStartup.cs :

Startfunktion in Startup.cs :

public Startup(IHostingEnvironment env)
{
    // Set up configuration sources.
    var builder = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .AddEnvironmentVariables();

    if (env.IsDevelopment())
    {
        // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
            builder.AddApplicationInsightsSettings(developerMode: true);
    }
    Configuration = builder.Build();
    Globals.Configuration = Configuration;
    Globals.HostingEnvironment = env;
    Globals.EnsureDatabaseCreated();
}

Und definieren Sie Globals.EnsureDatabaseCreated()wie folgt:

public static void EnsureDatabaseCreated()
    {
        var optionsBuilder = new DbContextOptionsBuilder();
        if (HostingEnvironment.IsDevelopment()) optionsBuilder.UseSqlServer(Configuration["Data:dev:DataContext"]);
        else if (HostingEnvironment.IsStaging()) optionsBuilder.UseSqlServer(Configuration["Data:staging:DataContext"]);
        else if (HostingEnvironment.IsProduction()) optionsBuilder.UseSqlServer(Configuration["Data:live:DataContext"]);
        var context = new ApplicationContext(optionsBuilder.Options);
        context.Database.EnsureCreated();

        optionsBuilder = new DbContextOptionsBuilder();
        if (HostingEnvironment.IsDevelopment()) optionsBuilder.UseSqlServer(Configuration["Data:dev:TransientContext"]);
        else if (HostingEnvironment.IsStaging()) optionsBuilder.UseSqlServer(Configuration["Data:staging:TransientContext"]);
        else if (HostingEnvironment.IsProduction()) optionsBuilder.UseSqlServer(Configuration["Data:live:TransientContext"]);
        new TransientContext(optionsBuilder.Options).Database.EnsureCreated();
    }

Zur Verwendung context.Database.Migrate()siehe hier oder hier .

James P.
quelle
Hallo James, danke für deine Antwort! Ich habe in meiner Startmethode keinen Zugriff auf einen vairable Namen Globals. Wie kann ich darauf zugreifen?
Bailando Bailando
2
Gleiche, nicht sehen a Globals. Dies scheint eine nicht standardmäßige Methode zu sein, um dies zu verhindern
Douglas Gaskell
Soweit ich weiß, wird der DbContext standardmäßig in Startup.ConfigureServices erstellt, jedoch über indirekte Methoden. Sie können es wieder dort draußen oder in Startup.Configure mit app.ApplicationServices.GetRequiredService <T> fischen. Ich denke.
Josh Sutterfield
6

Normalerweise DbContextwird das dem Abhängigkeitsinjektionsbehälter folgendermaßen hinzugefügt Startup.ConfigureServices():

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add DbContext to the injection container
        services.AddDbContext<MyDbContext>(options =>
                options.UseSqlServer(
                    this.Configuration.GetConnectionString("DefaultConnection")));
    }
}

Der fungiert IServiceCollectionjedoch nicht als Dienstanbieter, und da der vor dem aktuellen Bereich ( ) DbContextnicht beim Injektionscontainer registriert wurde , können wir hier nicht über die Abhängigkeitsinjektion auf den Kontext zugreifen.Startup.ConfigureServices

Henk Mollema bespricht manuell lösen Dienste während des Starts hier , aber erwähnt , dass ...

Das manuelle Auflösen von Diensten (auch bekannt als Service Locator) wird im Allgemeinen als Anti-Pattern angesehen ... [und] Sie sollten dies so weit wie möglich vermeiden.

Henk erwähnt auch, dass die StartupAbhängigkeitsinjektion des Konstruktors sehr begrenzt ist und keine in konfigurierten Dienste enthält Startup.ConfigureServices(), sodass die Verwendung von DbContext über den im Rest der App verwendeten Injektionscontainer am einfachsten und am besten geeignet ist.

Die Laufzeit der Hosting - Service - Provider können bestimmte Dienste in den Konstruktor der injizieren StartupKlasse, wie IConfiguration, IWebHostEnvironment( IHostingEnvironmentin Pre-3.0 - Versionen), ILoggerFactoryund IServiceProvider. Beachten Sie, dass letztere eine von der Hosting-Schicht erstellte Instanz ist und nur die wesentlichen Dienste zum Starten einer Anwendung enthält.

Um anzurufen Database.EnsureCreated()oder Database.Migrate(), können und möchten wir den DbContext automatisch auflösen lassen Startup.Configure(), wo unsere konfigurierten Dienste jetzt über DI verfügbar sind:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add DbContext to the injection container
        services.AddDbContext<MyDbContext>(options =>
                options.UseSqlServer(
                    this.Configuration.GetConnectionString("DefaultConnection")));
    }

    public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext context)
    {
        if (env.IsDevelopment())
        {
            context.Database.EnsureCreated();
            //context.Database.Migrate();
        }
    }
}

Bitte denken Sie daran , wie Bassam Alugili Antwort von EF Core - Dokumentation verwiesen , dass Database.EnsureCreated()und Database.Migrate()nicht zusammen sein sollen verwendet , da stellt man sicher , Ihre bestehende Migrationen auf die Datenbank angewendet werden, die bei Bedarf erstellt wird. Die andere Methode stellt lediglich sicher, dass eine Datenbank vorhanden ist, und erstellt, falls nicht, eine Datenbank, die Ihre Datenbank widerspiegelt DbContext, einschließlich aller über die Fluent-API im Kontext vorgenommenen Seeding-Vorgänge.

Kyllian Mobley
quelle
1

Außerdem kann es zu Leistungseinbußen kommen, wenn Sie dies im Konstruktor Ihres Kontexts aufrufen ... Nachdem EnsureCreatedich zum Dienstprogramm setup.cs gewechselt bin, habe ich erhebliche Verbesserungen meiner Antwortzeiten festgestellt.

Hinweis: Ich verwende EFC und UWP.

visc
quelle