9
votes

Comment amorcer dans Entity Framework Core 3.0?

J'essaie de semer la base de données avec des données, en utilisant ASP.NET CORE 3.0 et EF Core.

J'ai créé mon DbContext et conformément à la documentation , aux sources en ligne ou même aux questions EF Core 2.1 (je n'ai pas trouvé de changements de rupture sur ce sujet).

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Band>().HasData(
            new Band()
            {
                Id = Guid.Parse("e96bf6d6-3c62-41a9-8ecf-1bd23af931c9"),
                Name = "SomeName",
                CreatedOn = new DateTime(1980, 2, 13),
                Description = "SomeDescription"
            });

        base.OnModelCreating(modelBuilder);           
    }

Cela ne fait pas ce que j'attends: rien n'est amorcé au démarrage de l'application (même si pendant le débogage la méthode est appelée de quelque part).

Cependant, si j'ajoute une migration, la migration contient l'instruction d'insertion correspondante (qui n'est pas le type d'amorçage que je recherche).

Question: Quelle est la bonne façon d'effectuer la création de la base de données au démarrage de l'application?

Par graine de la base de données, je veux dire que je m'attends à ce que certaines données soient assurées dans certaines tables à chaque démarrage de l'application.


J'ai l'alternative pour créer une classe d'amorçage et la gérer après le Database.Migrate avec du code personnalisé, mais cela semble être une solution de contournement, car la documentation spécifie que OnModelCreating doit être utilisé pour amorcer les données).


Donc, à ma connaissance, après avoir lu les réponses et relu la documentation, ce qu'ils entendent par «graine» est une «initialisation» qui peut avoir lieu juste à côté du modèle de données (c'est pourquoi cela me semblait étrange - mélanger la création du modèle avec la partie ensemencement des données).


0 commentaires

5 Réponses :


3
votes

Si vous souhaitez amorcer au démarrage de l'application, dans la méthode de démarrage de vos applications, vous pouvez vérifier les données souhaitées à l'aide de vérifications conditionnelles et, en l'absence de retour, ajoutez ces classes au contexte et enregistrez les modifications.

L'amorçage dans EF Core est conçu pour la migration, ses données initialisées pour la base de données, pas pour un runtime d'applications. Si vous voulez qu'un ensemble de données reste inchangé, envisagez d'utiliser une méthode alternative? Comme le garder au format xml / json avec en mémoire cache via des propriétés avec des vérifications de champ.

Vous pouvez utiliser la syntaxe de suppression / création au démarrage de l'application, mais elle est généralement désapprouvée car l'état manque de permanence.

Malheureusement pour ce que vous voulez, cela doit être une solution car ce n'est pas dans la ligne attendue du fonctionnement d'EF.


2 commentaires

Je voulais également vous montrer ce problème pour savoir pourquoi ce n'est pas nécessairement une bonne idée pour les migrations au démarrage de l'application. github.com/dotnet/efcore/issues/19587 Il est préférable de contrôler globalement vos migrations et vous pouvez ajouter du code de migration supplémentaire lors de la création de la migration, puis contrôler étroitement quand et quelles bases de données sont modifiées. (puisque vous pouvez utiliser inmemory pour les branches de contrôle de source ou même les bases de données locales à la place). Dans l'ensemble, comme ci-dessus, si vous avez des modifications de démarrage d'application dans votre base de données, mieux vaut les faire avec l'application elle-même, mais si vous devez trouver une alternative.


Je veux semer quelques entités (rôles d'utilisateurs et autres). Je comprends ce que vous dites, qu'une graine personnalisée devrait être faite de ma part, car EF vise à résoudre d'autres types de "graine" via OnModelCreating ().



6
votes

Je ne pense pas OnModelCreating() soit le meilleur endroit pour semer votre base de données. Je pense que cela dépend entièrement du moment où vous voulez que votre logique d'amorçage s'exécute. Vous avez dit que vous souhaitiez que votre amorçage s'exécute au démarrage de l'application, que votre base de données comporte ou non des modifications de migration.

Je créerais une méthode d'extension pour accrocher dans la méthode Configure() dans la classe Startup.cs:

Startup.cs:

internal static GatewayDbContext AddOrUpdateSeedData(this GatewayDbContext dbContext)
        {
            var defaultBand = dbContext.Bands
                .FirstOrDefault(c => c.Id == Guid.Parse("e96bf6d6-3c62-41a9-8ecf-1bd23af931c9"));

            if (defaultBand == null)
            {
                defaultBand = new Band { ... };
                dbContext.Add(defaultBand);
            }
            return dbContext;
        }

MigrateAndSeedDb.cs

 public static void MigrateAndSeedDb(this IApplicationBuilder app, bool development = false)
        {
            using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
            using (var context = serviceScope.ServiceProvider.GetService<GatewayDbContext>())
            {
                //your development/live logic here eg:
                context.Migrate();
                if(development)
                    context.Seed();
            }                
        }

        private static void Migrate(this GatewayDbContext context)
        {
            context.Database.EnsureCreated();
            if (context.Database.GetPendingMigrations().Any())
                context.Database.Migrate();
        }

        private static void Seed(this GatewayDbContext context)
        {
            context.AddOrUpdateSeedData();
            context.SaveChanges();
        }

AddOrUpdateSeedData.cs

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.MigrateAndSeedDb(development: true);
            }
            else
            {
                 app.MigrateAndSeedDb(development: false);
            }           

            app.UseHttpsRedirection();
 ...


2 commentaires

Donc, à la fin, le chemin à parcourir est avec un code personnalisé ... J'apprécie le code pour la logique AddIfNotExists / Update.


La documentation Microsoft indique «N'appelez pas EnsureCreated () avant Migrate (). EnsureCreated () contourne les migrations pour créer le schéma, ce qui entraîne l'échec de Migrate ()». Avez-vous remarqué cela?



11
votes

si vous avez des données de départ complexes, la fonctionnalité principale d'EF par défaut n'est pas une bonne idée à utiliser. par exemple, vous ne pouvez pas ajouter vos données d'amorçage en fonction de vos configurations ou de votre environnement système.

J'utilise un service personnalisé et une injection de dépendances pour ajouter mes données d'amorçage et appliquer toutes les migrations en attente pour le contexte lorsque l'application démarre. Je partage mon service personnalisé espère qu'il aide:

IDbInitializer.cs

 // StartUp.cs -- Configure method
         var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory> ();
         using (var scope = scopeFactory.CreateScope ()) {
            var dbInitializer = scope.ServiceProvider.GetService<IDbInitializer> ();
            dbInitializer.Initialize ();
            dbInitializer.SeedData ();
         }

DbInitializer.cs

 // StartUp.cs -- ConfigureServices method
 services.AddScoped<IDbInitializer, DbInitializer> ()

pour utiliser ce service, vous pouvez l'ajouter à votre collection de services:

    public class DbInitializer : IDbInitializer {
        private readonly IServiceScopeFactory _scopeFactory;

        public DbInitializer (IServiceScopeFactory scopeFactory) {
            this._scopeFactory = scopeFactory;
        }

        public void Initialize () {
            using (var serviceScope = _scopeFactory.CreateScope ()) {
                using (var context = serviceScope.ServiceProvider.GetService<AppDbContext> ()) {
                    context.Database.Migrate ();
                }
            }
        }

        public void SeedData () {
            using (var serviceScope = _scopeFactory.CreateScope ()) {
                using (var context = serviceScope.ServiceProvider.GetService<AppDbContext> ()) {

                    //add admin user
                    if (!context.Users.Any ()) {
                        var adminUser = new User {
                            IsActive = true,
                            Username = "admin",
                            Password = "admin1234", // should be hash
                            SerialNumber = Guid.NewGuid ().ToString ()
                        };
                        context.Users.Add (adminUser);
                    }

                    context.SaveChanges ();
                }
            }
        }
    }

parce que je veux utiliser ce service chaque fois que mon programme démarre, j'utilise le service injecté de cette façon:

    public interface IDbInitializer
    {
        /// <summary>
        /// Applies any pending migrations for the context to the database.
        /// Will create the database if it does not already exist.
        /// </summary>
        void Initialize();

        /// <summary>
        /// Adds some default values to the Db
        /// </summary>
        void SeedData();
    }


3 commentaires

Ceci est similaire à ce que j'ai fait comme "solution de contournement", sauf que j'appelle directement dbInitialized.SeedData (scope.ServiceProvider).


Je pense que pour l'instant, c'est la meilleure façon de gérer les données de base ef-core dans les applications de base asp.net. car de cette façon, vous pouvez utiliser votre configuration et modifier votre amorçage en fonction de celle-ci.


Comment utiliser ce type de démarrage pour tester avec MSTest?



2
votes

Vous pouvez utiliser les migrations pour cela. Créez simplement une nouvelle migration (sans modifications préalables des classes de modèle). La classe de migration générée aurait des méthodes Up () et Down () vides. Là, vous pouvez faire votre semis. Comme:

protected override void Up(MigrationBuilder migrationBuilder)
{
  migrationBuilder.Sql("your sql statement here...");
}

et c'est tout.


0 commentaires

1
votes

Juste comme ça

  public static class SeedDatabase
{
    public static void Initialize(IServiceProvider serviceProvider)
    {
        using (var context = new HospitalManagementDbContext(serviceProvider.GetRequiredService<DbContextOptions<HospitalManagementDbContext>>()))
        {
            if (context.InvestigationTags.Any())
            {
                return;
            }

            context.InvestigationTags.AddRange(
                new Models.InvestigationTag
                {
                    Abbreviation = "A1A",
                    Name = "Alpha-1 Antitrypsin"
                },

                new Models.InvestigationTag
                {
                    Abbreviation = "A1c",
                    Name = "Hemoglobin A1c"
                },


                new Models.InvestigationTag
                {
                    Abbreviation = "Alk Phos",
                    Name = "Alkaline Phosphatase"
                }
                );
            context.SaveChanges();
        }
    }
}

Classe SeedDatabase:

 public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            try
            {
                SeedDatabase.Initialize(services);
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occured seeding the DB");
            }
        }
        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var hb = Host.CreateDefaultBuilder(args)
             .ConfigureWebHostDefaults(webBuilder =>
             {
                 webBuilder.UseStartup<Startup>();
             });
        return hb;
    }
   
       
}


0 commentaires