c# - many - ¿Cómo sembrar en Entity Framework Core 2?
include entity framework core (6)
Tengo dos mesas y quiero rellenarlas utilizando semillas.
Yo uso ASP.NET Core 2 en Ubuntu.
¿Cómo rellenar los datos de dos tablas, una con una clave externa? El caudalímetro tiene muchas notas y la nota pertenece al flómetro. Quiero hacer algo como esto, pero debería estar almacenado en la base de datos.
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}
}
}
A partir de Entity Framework Core 2.1, ahora existe un nuevo método para sembrar datos. En su clase OnModelCreating
anule OnModelCreating
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Url = "http://sample.com" });
}
Y para las entidades relacionadas, use clases anónimas y especifique la clave externa de la entidad relacionada:
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"});
Tenga en cuenta que necesitará ejecutar una migración adicional después de ingresar estos datos en su método OnModelCreating y Update-Database para actualizar sus datos.
Los documentos oficiales han sido updated .
Creé mis semillas en json y simplemente las agregué en mi Asp.net core Startup
Muy similar a https://garywoodfine.com/how-to-seed-your-ef-core-database/
No encontré una solución lista para usar todavía.
Esta es mi solución para EF Core 2.0, adaptada de https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database-initialization-code
En program.cs
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Seed().Run();
}
....
Entonces mi clase sembradora
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;
}
}
Me encontré con la misma pregunta y arreglé la siembra de la siguiente manera:
Primero agregué el public static bool AllMigrationsApplied(this DbContext context)
de https://garywoodfine.com/how-to-seed-your-ef-core-database/ a mi modelo.
Luego implementé un ámbito de servicio para sembrar la base de datos -> ver este blog
Luego hice un public static void EnsureSeedData
con el código para generar datos de prueba usando NBuilder y Faker siguiendo el tutorial en este blog
Espero que esto ayude a las personas a implementar una semilla de prueba automatizada para sus proyectos. Actualmente estoy ocupado implementándolo, cuando tenga tiempo, publicaré algunos ejemplos de código sobre cómo hacerlo.
No me gusta el enfoque HasData que el que se ha escrito en la documentación de Microsoft porque no puedo mantener mis migraciones limpias de esta manera y porque OnModelCreating()
en mi DbContext
comienza a depender de datos que se sienten un poco mal y causan problemas con el generador de datos aleatorios.
Para mí, la forma más eficiente y cómoda es crear una clase semilla para cada uno de mis DbSets que tenga este aspecto. (Con la biblioteca Bogus es tan fácil como respirar)
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;
}
Luego cree el controlador de servicio, que solo funciona en el entorno de desarrollo.
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);
}
}
Y llámalo desde Insomnia / Postman cuando yo quiera.
tl; dr : Eche un vistazo a mi proyecto dwCheckApi para ver cómo lo he implementado.
Como han dicho otros, puede leer sus datos de semilla de JSON o similar (de esa manera puede controlarlos desde el origen, si lo desea).
La forma en que lo he implementado en mis proyectos es tener un método al que se llama en el método Configure
en la clase de Inicio (solo cuando está en desarrollo):
if (env.IsDevelopment())
{
app.EnsureDatabaseIsSeeded(false);
}
que llama a lo siguiente:
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();
}
}
My DbContext es de tipo DwContext
que es una clase que extiende el tipo EF Core DbContext
El método de extensión EnsureSeedData
se parece a esto:
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''s 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;
}
Esta aplicación está diseñada para mostrar las relaciones entre libros, personajes y series. Por eso hay tres sembradoras.
Y uno de esos métodos de sembradora se parece a esto:
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();
}
Probablemente haya algún código aquí que no es bueno, pero podría ser un punto de partida para que rebotes.
Debido a que solo se invoca a los sembradores cuando se está en el entorno de desarrollo, deberá asegurarse de que su aplicación se inicie de esa manera (si se inicia desde la línea de comandos, puede usar ASPNETCORE_ENVIRONMENT=Development dotnet run
para asegurarse de que se inicia en el desarrollo).
También significa que necesitará un enfoque diferente para sembrar su base de datos en producción. En dwCheckApi, tengo un controlador al que se puede llamar para inicializar la base de datos (eche un vistazo al método SeedData del DatabaseController para ver cómo lo hago).