From 0f08b53d45e2c723a80e2b01be4e127e35923c87 Mon Sep 17 00:00:00 2001 From: maumar Date: Sun, 13 Oct 2024 21:56:07 -0700 Subject: [PATCH 1/2] whats new - seeding --- .../core/modeling/data-seeding.md | 5 + .../core/what-is-new/ef-core-9.0/whatsnew.md | 9 ++ .../NewInEFCore9/DataSeedingContext.cs | 104 ++++++++++++++++++ .../NewInEFCore9/DataSeedingSample.cs | 25 +++++ .../Miscellaneous/NewInEFCore9/Program.cs | 5 +- 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 samples/core/Miscellaneous/NewInEFCore9/DataSeedingContext.cs create mode 100644 samples/core/Miscellaneous/NewInEFCore9/DataSeedingSample.cs diff --git a/entity-framework/core/modeling/data-seeding.md b/entity-framework/core/modeling/data-seeding.md index a4dc991684..8a2bf75d93 100644 --- a/entity-framework/core/modeling/data-seeding.md +++ b/entity-framework/core/modeling/data-seeding.md @@ -11,10 +11,15 @@ Data seeding is the process of populating a database with an initial set of data There are several ways this can be accomplished in EF Core: +* Configuration options seeding methods * Model seed data * Manual migration customization * Custom initialization logic + + +## Configuration options `UseSeeding` and `UseAsyncSeeding` methods + ## Model seed data Unlike in EF6, in EF Core, seeding data can be associated with an entity type as part of the model configuration. Then EF Core [migrations](xref:core/managing-schemas/migrations/index) can automatically compute what insert, update or delete operations need to be applied when upgrading the database to a new version of the model. diff --git a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md index c11f9aba04..19ea2ed158 100644 --- a/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md @@ -1377,6 +1377,15 @@ FROM "Walks" AS "w" INNER JOIN "Pubs" AS "p" ON "w"."ClosestPubId" = "p"."Id" ``` +### Data seeding + +EF9 introduced a convenient way to perform data seeding, that is populating the database with initial data. `DbContextOptionsBuilder` now contains `UseSeeding` and `UseAsyncSeeding` methods which get executed every time the DbContext is initialized. + +> !NOTE +> If the application had ran previously, the database may already contain the sample data (which would have been added on the first initialization of the context). As such, `UseSeeding` `UseAsyncSeeding` should check if data exists before attempting to populate the database. This can be achieved by issueing a simple EF query. + +More information and an example can be found [here](/core/modeling/data-seeding#use-seeding-method). + ### Specify fill-factor for keys and indexes diff --git a/samples/core/Miscellaneous/NewInEFCore9/DataSeedingContext.cs b/samples/core/Miscellaneous/NewInEFCore9/DataSeedingContext.cs new file mode 100644 index 0000000000..1c9235c48f --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore9/DataSeedingContext.cs @@ -0,0 +1,104 @@ +namespace NewInEfCore9; + +public class Writer +{ + public int Id { get; set; } + public string FirstName { get; set; } = null!; + public string LastName { get; set; } = null!; + + public List BooksWritten { get; set; } = null!; +} + +public class Book +{ + public int Id { get; set; } + public string Title { get; set; } = null!; + public DateTime PublishDate { get; set; } + public Series? PartOf { get; set; } +} + +public class Series +{ + public int Id { get; set; } + public string Name { get; set; } = null!; + public List Books { get; set; } = null!; + public bool Completed { get; set; } +} + +public class DataSeedingContext : DbContext +{ + public DbSet Writers => Set(); + public DbSet Books => Set(); + public DbSet Series => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + #region DataSeedingConfiguration + optionsBuilder.UseSqlServer(@$"Server=(localdb)\mssqllocaldb;Database=DataSeedingContext;ConnectRetryCount=0") + .UseAsyncSeeding(async (ctx, b, ct) => + { + if (!ctx.Set().Any() + && !ctx.Set().Any() + && !ctx.Set().Any()) + { + var firstLawSeries = new Series { Id = 1, Name = "First Law", Completed = true }; + var joeAbercrombie = new Writer + { + Id = 1, + FirstName = "Joe", + LastName = "Abercrombie", + BooksWritten = + [ + new Book { Id = 1, Title = "The Blade Itself", PartOf = firstLawSeries, PublishDate = new DateTime(2006, 5, 4) }, + new Book { Id = 2, Title = "Before They Are ...", PartOf = firstLawSeries, PublishDate = new DateTime(2007, 3, 1) }, + new Book { Id = 3, Title = "Last Argument of Kings", PartOf = firstLawSeries, PublishDate = new DateTime(2008, 3, 20)}, + ] + }; + + var stormlightArchive = new Series { Id = 2, Name = "The Stormlight Archive", Completed = false }; + var brandonSanderson = new Writer + { + Id = 2, + FirstName = "Brandon", + LastName = "Sanderson", + BooksWritten = + [ + new Book { Id = 4, Title = "Elantris", PublishDate = new DateTime(2005, 4, 21) }, + new Book { Id = 5, Title = "The Way of Kings", PartOf = stormlightArchive, PublishDate = new DateTime(2010, 8, 31) }, + new Book { Id = 6, Title = "Words of Radiance", PartOf = stormlightArchive, PublishDate = new DateTime(2014, 3, 1) }, + new Book { Id = 7, Title = "Oathbringer", PartOf = stormlightArchive, PublishDate = new DateTime(2017, 11, 14) }, + new Book { Id = 8, Title = "Rhythm of War", PartOf = stormlightArchive, PublishDate = new DateTime(2020, 11, 17) }, + ] + }; + + var duneSeries = new Series { Id = 3, Name = "Dune", Completed = true }; + var frankHerbert = new Writer + { + Id = 3, + FirstName = "Frank", + LastName = "Herbert", + BooksWritten = + [ + new Book { Id = 9, Title = "Dune", PartOf = duneSeries, PublishDate = new DateTime(1965, 8, 1) }, + new Book { Id = 10, Title = "Dune Messiah", PartOf = duneSeries, PublishDate = new DateTime(1969, 6, 1) }, + new Book { Id = 11, Title = "Children of Dune", PartOf = duneSeries, PublishDate = new DateTime(1976, 4, 1) }, + new Book { Id = 12, Title = "Heretics of Dune", PartOf = duneSeries, PublishDate = new DateTime(1984, 1, 1) }, + new Book { Id = 13, Title = "Chapterhouse: Dune", PartOf = duneSeries, PublishDate = new DateTime(1985, 4, 1) }, + ] + }; + + ctx.Set().AddRange(joeAbercrombie, frankHerbert); + await ctx.SaveChangesAsync(ct); + } + }); + #endregion + optionsBuilder.EnableSensitiveDataLogging() + .LogTo(Console.WriteLine, LogLevel.Information); +} diff --git a/samples/core/Miscellaneous/NewInEFCore9/DataSeedingSample.cs b/samples/core/Miscellaneous/NewInEFCore9/DataSeedingSample.cs new file mode 100644 index 0000000000..1e5eeac8b5 --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore9/DataSeedingSample.cs @@ -0,0 +1,25 @@ +using NetTopologySuite.Operation.Valid; + +namespace NewInEfCore9; + +public static class DataSeedingSample +{ + public static async Task Data_seeding_in_EF9() + { + PrintSampleName(); + + await using var context = new DataSeedingContext(); + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); + + await using var secondContext = new DataSeedingContext(); + // seeding method gets invoked here also + await context.Database.EnsureCreatedAsync(); + } + + private static void PrintSampleName([CallerMemberName] string? methodName = null) + { + Console.WriteLine($">>>> Sample: {methodName}"); + Console.WriteLine(); + } +} diff --git a/samples/core/Miscellaneous/NewInEFCore9/Program.cs b/samples/core/Miscellaneous/NewInEFCore9/Program.cs index 181190ccbf..e0b0e12900 100644 --- a/samples/core/Miscellaneous/NewInEFCore9/Program.cs +++ b/samples/core/Miscellaneous/NewInEFCore9/Program.cs @@ -4,7 +4,6 @@ public class Program { public static async Task Main() { - await PrimitiveCollectionsSample.Queries_using_readonly_primitive_collections(); await PrimitiveCollectionsSample.Queries_using_readonly_primitive_collections_SQLite(); @@ -20,7 +19,7 @@ public static async Task Main() await NullSemanticsSample.Null_semantics_improvements_in_EF9(); await NullSemanticsSample.Null_semantics_improvements_in_EF9_on_SQLite(); - + await CustomConventionsSample.Conventions_enhancements_in_EF9(); await JsonColumnsSample.Columns_from_JSON_are_pruned_when_needed(); @@ -36,5 +35,7 @@ public static async Task Main() await DateOnlyTimeOnlySample.Can_use_DateOnly_TimeOnly_on_SQLite(); await DateOnlyTimeOnlySample.Can_use_DateOnly_TimeOnly_on_SQL_Server(); await DateOnlyTimeOnlySample.Can_use_DateOnly_TimeOnly_on_SQL_Server_with_JSON(); + + await DataSeedingSample.Data_seeding_in_EF9(); } } From 2d660ca686e082f0033d04d1a24a6942f8291a8d Mon Sep 17 00:00:00 2001 From: maumar Date: Mon, 14 Oct 2024 16:34:53 -0700 Subject: [PATCH 2/2] gf --- samples/core/Modeling/DataSeeding/Blog.cs | 16 +++---- samples/core/Modeling/DataSeeding/Country.cs | 10 ++++ .../DataSeeding/DataSeedingContext.cs | 48 ++++++++++++------- samples/core/Modeling/DataSeeding/Language.cs | 10 ++++ samples/core/Modeling/DataSeeding/Post.cs | 18 +++---- 5 files changed, 69 insertions(+), 33 deletions(-) create mode 100644 samples/core/Modeling/DataSeeding/Country.cs create mode 100644 samples/core/Modeling/DataSeeding/Language.cs diff --git a/samples/core/Modeling/DataSeeding/Blog.cs b/samples/core/Modeling/DataSeeding/Blog.cs index a80819e484..93de265457 100644 --- a/samples/core/Modeling/DataSeeding/Blog.cs +++ b/samples/core/Modeling/DataSeeding/Blog.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; +//using System.Collections.Generic; -namespace EFModeling.DataSeeding; +//namespace EFModeling.DataSeeding; -public class Blog -{ - public int BlogId { get; set; } - public string Url { get; set; } +//public class Blog +//{ +// public int BlogId { get; set; } +// public string Url { get; set; } - public virtual ICollection Posts { get; set; } -} \ No newline at end of file +// public virtual ICollection Posts { get; set; } +//} diff --git a/samples/core/Modeling/DataSeeding/Country.cs b/samples/core/Modeling/DataSeeding/Country.cs new file mode 100644 index 0000000000..cecba3ae2f --- /dev/null +++ b/samples/core/Modeling/DataSeeding/Country.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace EFModeling.DataSeeding; + +public class Country +{ + public int CountryId { get; set; } + public string Name { get; set; } + public virtual ICollection OfficialLanguages { get; set; } +} diff --git a/samples/core/Modeling/DataSeeding/DataSeedingContext.cs b/samples/core/Modeling/DataSeeding/DataSeedingContext.cs index 33624e27e0..9e3fab426e 100644 --- a/samples/core/Modeling/DataSeeding/DataSeedingContext.cs +++ b/samples/core/Modeling/DataSeeding/DataSeedingContext.cs @@ -4,8 +4,11 @@ namespace EFModeling.DataSeeding; public class DataSeedingContext : DbContext { - public DbSet Blogs { get; set; } - public DbSet Posts { get; set; } + public DbSet Countries { get; set; } + public DbSet Languages { get; set; } + + //public DbSet Blogs { get; set; } + //public DbSet Posts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder @@ -13,34 +16,47 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(entity => { entity.Property(e => e.Url).IsRequired(); }); + modelBuilder.Entity(entity => { entity.Property(e => e.Name).IsRequired(); }); + //modelBuilder.Entity(entity => { entity.Property(e => e.Url).IsRequired(); }); #region BlogSeed modelBuilder.Entity().HasData(new Blog { BlogId = 1, Url = "http://sample.com/" }); #endregion - modelBuilder.Entity( + modelBuilder.Entity( entity => { - entity.HasOne(d => d.Blog) - .WithMany(p => p.Posts) - .HasForeignKey("BlogId"); + entity.HasMany(x => x.UsedIn) + .WithMany(x => x.OfficialLanguages) + .UsingEntity( + "LanguageCountry", + r => r.HasOne(typeof(Country)).WithMany().HasForeignKey("CountryId").HasPrincipalKey(nameof(Country.CountryId)), + l => l.HasOne(typeof(Language)).WithMany().HasForeignKey("LanguageId").HasPrincipalKey(nameof(Language.Id)), + j => j.HasKey("LanguageId", "CountryId")); }); + //modelBuilder.Entity( + // entity => + // { + // entity.HasOne(d => d.Blog) + // .WithMany(p => p.Posts) + // .HasForeignKey("BlogId"); + // }); + #region PostSeed modelBuilder.Entity().HasData( new Post { BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1" }); #endregion - #region AnonymousPostSeed - modelBuilder.Entity().HasData( - new { BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2" }); - #endregion + //#region AnonymousPostSeed + //modelBuilder.Entity().HasData( + // new { BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2" }); + //#endregion - #region OwnedTypeSeed - modelBuilder.Entity().OwnsOne(p => p.AuthorName).HasData( - new { PostId = 1, First = "Andriy", Last = "Svyryd" }, - new { PostId = 2, First = "Diego", Last = "Vega" }); - #endregion + //#region OwnedTypeSeed + //modelBuilder.Entity().OwnsOne(p => p.AuthorName).HasData( + // new { PostId = 1, First = "Andriy", Last = "Svyryd" }, + // new { PostId = 2, First = "Diego", Last = "Vega" }); + //#endregion } } diff --git a/samples/core/Modeling/DataSeeding/Language.cs b/samples/core/Modeling/DataSeeding/Language.cs new file mode 100644 index 0000000000..e7d188fead --- /dev/null +++ b/samples/core/Modeling/DataSeeding/Language.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace EFModeling.DataSeeding; + +public class Language +{ + public int Id { get; set; } + public string Name { get; set; } + public List UsedIn { get; set; } +} diff --git a/samples/core/Modeling/DataSeeding/Post.cs b/samples/core/Modeling/DataSeeding/Post.cs index 52e2fabb70..c960e6ec44 100644 --- a/samples/core/Modeling/DataSeeding/Post.cs +++ b/samples/core/Modeling/DataSeeding/Post.cs @@ -1,11 +1,11 @@ namespace EFModeling.DataSeeding; -public class Post -{ - public int PostId { get; set; } - public string Content { get; set; } - public string Title { get; set; } - public int BlogId { get; set; } - public Blog Blog { get; set; } - public Name AuthorName { get; set; } -} \ No newline at end of file +//public class Post +//{ +// public int PostId { get; set; } +// public string Content { get; set; } +// public string Title { get; set; } +// public int BlogId { get; set; } +// public Blog Blog { get; set; } +// public Name AuthorName { get; set; } +//}