Как засеять в Entity Framework Core 2?

У меня есть две таблицы, и я хочу заполнить их, используя семена.

Я использую ASP.NET Core 2 в Ubuntu.

Как заполнить данные для двух таблиц, где одна связана с другой через внешний ключ?

У расходомера есть много примечаний, и примечание принадлежит Расходомеру.

Я хочу сделать что-то подобное, но оно должно храниться в базе данных:

new Flowmeter 
{
    Make = "Simple model name",
    SerialNum = 45, 
    Model = "Lor Avon", 
    Notes = new List<Note>()
    {
        new Note() { Value = 45, CheckedAt = System.DateTime.Now },
        new Note() { Value = 98, CheckedAt = System.DateTime.Now }
    }
}

Ответ 1

Начиная с Entity Framework Core 2.1 теперь существует новый метод заполнения данных. В вашем DbContext переопределении класса OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Url = "http://sample.com" });
}

А для связанных сущностей используйте анонимные классы и укажите внешний ключ связанной сущности:

modelBuilder.Entity<Post>().HasData(
    new {BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1"},
    new {BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2"});

Важно: Обратите внимание, что вам нужно будет выполнить миграцию надстройки после ввода этих данных в метод OnModelCreating и Update-Database для обновления ваших данных.

Официальные документы были обновлены.

Ответ 2

Это мое решение для EF Core 2.0, адаптированное с https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database-initialization-code

В программе .cs

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Seed().Run();
    }

....

Тогда мой класс сеялки

public static class DatabaseSeedInitializer
{
    public static IWebHost Seed(this IWebHost host)
    {
        using (var scope = host.Services.CreateScope())
        {
            var serviceProvider = scope.ServiceProvider;

            try
            {
                Task.Run(async () =>
                {
                    var dataseed = new DataInitializer();
                    await dataseed.InitializeDataAsync(serviceProvider);
                }).Wait();

            }
            catch (Exception ex)
            {
                var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred seeding the DB.");
            }
        }
        return host;
    }
}

Ответ 3

tl;dr. Просмотрите мой проект dwCheckApi, чтобы узнать, как я его реализовал.

Как уже говорили другие, вы можете читать свои начальные данные из JSON или аналогичного (таким образом, если хотите, вы можете контролировать исходный код).

Способ, которым я реализовал это в своих проектах, состоит в том, чтобы иметь метод, который вызывается в методе Configure в классе Startup (только во время разработки):

if (env.IsDevelopment())
{
  app.EnsureDatabaseIsSeeded(false);
}

который вызывает следующее:

public static int EnsureDatabaseIsSeeded(this IApplicationBuilder applicationBuilder,
 bool autoMigrateDatabase)
{
    // seed the database using an extension method
    using (var serviceScope = applicationBuilder.ApplicationServices
   .GetRequiredService<IServiceScopeFactory>().CreateScope())
   {
       var context = serviceScope.ServiceProvider.GetService<DwContext>();
       if (autoMigrateDatabase)
       {
           context.Database.Migrate();
       }
       return context.EnsureSeedData();
   }
}

Мой DbContext имеет тип DwContext, который является классом, расширяющим тип EF Core DbContext

Метод расширения EnsureSeedData выглядит следующим образом:

public static int EnsureSeedData(this DwContext context)
{
    var bookCount = default(int);
    var characterCount = default(int);
    var bookSeriesCount = default(int);

    // Because each of the following seed method needs to do a save
    // (the data they're importing is relational), we need to call
    // SaveAsync within each method.
    // So let keep tabs on the counts as they come back

    var dbSeeder = new DatabaseSeeder(context);
    if (!context.Books.Any())
    {
        var pathToSeedData = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "BookSeedData.json");
        bookCount = dbSeeder.SeedBookEntitiesFromJson(pathToSeedData).Result;
    }
    if (!context.BookCharacters.Any())
    {
        characterCount = dbSeeder.SeedBookCharacterEntriesFromJson().Result;
    }
    if (!context.BookSeries.Any())
    {
        bookSeriesCount = dbSeeder.SeedBookSeriesEntriesFromJson().Result;
    }

    return bookCount + characterCount + bookSeriesCount;
}

Это приложение предназначено, чтобы показать отношения между книгами, персонажами и сериалами. Вот почему есть три сеялки.

И один из этих методов сеялки выглядит так:

public async Task<int> SeedBookEntitiesFromJson(string filePath)
{
    if (string.IsNullOrWhiteSpace(filePath))
    {
        throw new ArgumentException($"Value of {filePath} must be supplied to {nameof(SeedBookEntitiesFromJson)}");
    }
    if (!File.Exists(filePath))
    {
        throw new ArgumentException($"The file { filePath} does not exist");
    }
    var dataSet = File.ReadAllText(filePath);
    var seedData = JsonConvert.DeserializeObject<List<Book>>(dataSet);

    // ensure that we only get the distinct books (based on their name)
    var distinctSeedData = seedData.GroupBy(b => b.BookName).Select(b => b.First());

    _context.Books.AddRange(distinctSeedData);
    return await _context.SaveChangesAsync();
}

Возможно, здесь есть некоторый код, который не очень хорош, но он может стать отправной точкой для отскока.

Поскольку сеялки вызываются только в среде разработки, вам нужно убедиться, что ваше приложение запускается именно таким образом (если вы запускаете из командной строки, вы можете использовать ASPNETCORE_ENVIRONMENT=Development dotnet run, чтобы убедиться, что оно запускается в разработке).

Это также означает, что вам понадобится другой подход к заполнению вашей базы данных в рабочей среде. В dwCheckApi у меня есть контроллер, который можно вызвать для заполнения базы данных (посмотрите, как я это делаю, DatabaseController SeedData метод).

Ответ 4

Мне не нравится подход HasData, который был написан в документации Microsoft, потому что я не могу поддерживать чистоту своих миграций таким образом и потому что OnModelCreating() в моем DbContext начинает зависеть от данных, которые кажутся немного неправильными и вызывает проблемы с генератором случайных данных.

Для меня самый эффективный и удобный способ - создать начальный класс для каждого из моих DbSets, который выглядит следующим образом. (С библиотекой Bogus это так же просто, как дышать)

using Bogus;

        // namespace, class, etc.


        // CategorySeeder seed method
        public int Seed(AppDbContext context)
        {


            var faker = new Faker<Category>()
                .RuleFor(r => r.IsGroup, () => true)
                .RuleFor(r => r.Parent, () => null)
                .RuleFor(r => r.UniversalTimeTicks, () => DateTime.Now.ToUniversalTime().Ticks)
                .RuleFor(r => r.Title, f => "Folder: " + f.Random.Word());

            var folders1 = faker.Generate(5);

            faker.RuleFor(r => r.Parent, () => folders1.OrderBy(r => Guid.NewGuid()).First());
            var folders2 = faker.Generate(10);
            var folders3 = folders1.Concat(folders2).ToArray();

            faker.RuleFor(r => r.Parent, () => folders3.OrderBy(r => Guid.NewGuid()).First());
            faker.RuleFor(r => r.Title, f => f.Random.Word());
            faker.RuleFor(r => r.IsGroup, () => false);

            var elements = faker.Generate(20);

            var allSeeds = elements.Concat(folders3).ToArray();

            context.AddRange(allSeeds);
            context.SaveChanges();
            return allSeeds.Length;
        }

        // ProductSeeder Seed method
        public int Seed(AppDbContext context)
        {
            var faker = new Faker<Product>()
                .RuleFor(r => r.Sku, f => f.Random.AlphaNumeric(8))
                .RuleFor(r => r.Title, f => f.Random.Word())
                .RuleFor(r => r.Category, () => context.Categories.Where(c => !c.IsGroup).OrderBy(o => Guid.NewGuid()).First());

            var prod = faker.Generate(50);
            context.AddRange(prod);
            context.SaveChanges();
            return prod.Count;
        }

Затем создайте сервисный контроллер, который работает только в среде разработки.

    public class DataGeneratorController : BaseController
    {
        public DataGeneratorController(IServiceProvider sp) : base(sp) { }

        public IActionResult SeedData()
        {
            var lst = new List<string>();

            if (!_dbContext.Categories.Any())
            {
                var count = new CategoryConfiguration().Seed(_dbContext);
                lst.Add($"{count} Categories have been seeded.");
            }

            if (!_dbContext.Products.Any())
            {
                var count = new ProductConfiguration().Seed(_dbContext);
                lst.Add($"{count} Products have been seeded.");
            }

            if (lst.Count == 0)
            {
                lst.Add("Nothing has been seeded.");
            }

            return Json(lst);
        }
    }

И звони из Бессонницы\Почтальона, когда захочу.

Ответ 5

Я создал свои семена в json, и просто добавлю их к своему основному запуску Asp.net

Очень похоже на https://garywoodfine.com/how-to-seed-your-ef-core-database/

Не удалось найти готовое решение.

Ответ 6

Я столкнулся с одним и тем же вопросом, и я установил посев следующим образом:

Сначала я добавил public static bool AllMigrationsApplied(this DbContext context) из garywoodfine в мою модель.

Затем я применил область обслуживания, чтобы засеять db → см. этот блог

Затем я сделал public static void EnsureSeedData с кодом для генерации тестовых данных, используя NBuilder и Faker, следуя руководству этот блог

Я надеюсь, что это поможет людям внедрить автоматическое тестовое семя для своих проектов. В настоящее время я занимаюсь реализацией этого сам, когда у меня есть время, я отправлю некоторые примеры кода о том, как это сделать.

Ответ 7

Если кто-то еще заинтересовался этой темой, мы создали набор инструментов (глобальный инструмент .net и библиотека), которые упрощают процесс заполнения данных.

Короче говоря: вы можете сохранить содержимое вашей текущей БД в несколько файлов JSON или XML, а затем добавить в свое приложение несколько строк кода, который загружает эти файлы и импортирует данные, сохраненные там, в вашу БД. Набор инструментов абсолютно бесплатный и с открытым исходным кодом.

Подробные пошаговые инструкции опубликованы здесь.