diff --git a/entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md index bde4a52e99..226684e608 100644 --- a/entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md @@ -3313,3 +3313,217 @@ LEFT JOIN ( ) AS [t] ON [b].[Id] = [t].[BlogId] ORDER BY [b].[Id], [t].[Title] ``` + +## DbContext API and behavior enhancements + +EF7 contains a variety of small improvements to and related classes. + +> [!TIP] +> The code for samples in this section comes from [DbContextApiSample.cs](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs). + +### Suppressor for uninitialized DbSet properties + +Public, settable `DbSet` properties on a `DbContext` are automatically initialized by EF Core when the `DbContext` is constructed. For example, consider the following `DbContext` definition: + +```csharp +public class SomeDbContext : DbContext +{ + public DbSet Blogs { get; set; } +} +``` + +The `Blogs` property will be set to a `DbSet` instance as part of constructing the `DbContext` instance. This allows the context to be used for queries without any additional steps. + +However, following the introduction of [C# nullable reference types](/dotnet/csharp/tutorials/nullable-reference-types), the compiler now warns that the non-nullable property `Blogs` is not initialized: + +> `[CS8618] Non-nullable property 'Blogs' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.` + +This is a bogus warning; the property is set to a non-null value by EF Core. Also, declaring the property as nullable will make the warning go away, but this is not a good idea because, conceptually, the property is not nullable and will never be null. + +EF7 contains a [DiagnosticSuppressor](/dotnet/fundamentals/code-analysis/suppress-warnings) for `DbSet` properties on a `DbContext` which stops the compiler generating this warning. + +> [!TIP] +> This pattern originated in the days when C# auto-properties were very limited. With modern C#, consider making the auto-properties read-only, and then either initialize them explicitly in the `DbContext` constructor, or obtain the cached `DbSet` instance from the context when needed. For example, `public DbSet Blogs => Set()`. + +### Distinguish cancellation from failure in logs + +Sometimes an application will explicitly cancel a query or other database operation. This is usually done using a passed to the method performing the operation. + +In EF Core 6, the events logged when an operation is canceled are the same as those logged when the operation fails for some other reason. EF7 introduces new log events specifically for canceled database operations. These new events are, by default, logged at the level. The following table shows the relevant events and their default log levels: + +| Event | Description | Default log level | +|------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------|-------------------| +| | An error occurred while processing the results of a query. | `LogLevel.Error` | +| | An error occurred while attempting to save changes to the database. | `LogLevel.Error` | +| | An error occurred while a database command was executing. | `LogLevel.Error` | +| [CoreEventId.QueryCanceled](https://github.com/dotnet/efcore/blob/main/src/EFCore/Diagnostics/CoreEventId.cs) | A query was canceled. | `LogLevel.Debug` | +| [CoreEventId.SaveChangesCanceled](https://github.com/dotnet/efcore/blob/main/src/EFCore/Diagnostics/CoreEventId.cs) | The database command was canceled while attempting to save changes. | `LogLevel.Debug` | +| [SaveChangesCanceled.CommandCanceled](https://github.com/dotnet/efcore/blob/main/src/EFCore.Relational/Diagnostics/RelationalEventId.cs) | The execution of a `DbCommand` has been canceled. | `LogLevel.Debug` | + +> [!NOTE] +> Cancellation is detected by looking at the exception rather than checking cancellation token. This means that cancellations not triggered via the cancellation token will still be detected and logged in this way. + +### New `IProperty` and `INavigation` overloads for `EntityEntry` methods + +Code working with the EF model will often have an or representing property or navigation metadata. An [EntityEntry](xref:core/change-tracking/entity-entries) is then used to get the property/navigation value or query its state. However, prior to EF7, this required passing the _name_ of the property or navigation to methods of the `EntityEntry`, which would then re-lookup the `IProperty` or `INavigation`. In EF7, the `IProperty` or `INavigation` can instead be passed directly, avoiding the additional lookup. + +For example, consider a method to find all the siblings of a given entity: + + +[!code-csharp[FindSiblings](../../../../samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs?name=FindSiblings)] + +This method finds the parent of a given entity, and then passes the inverse `INavigation` to the `Collection` method of the parent entry. This metadata is then used to return all siblings of the given parent. Here's an example of its use: + + +[!code-csharp[UseFindSiblings](../../../../samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs?name=UseFindSiblings)] + +And the output: + +```output +Siblings to 1: 'Announcing Entity Framework 7 Preview 7: Interceptors!' are... + 5: 'Productivity comes to .NET MAUI in Visual Studio 2022' + 6: 'Announcing .NET 7 Preview 7' + 7: 'ASP.NET Core updates in .NET 7 Preview 7' +``` + +### `EntityEntry` for shared-type entity types + +EF Core can use the same CLR type for multiple different entity types. These are known as "shared-type entity types", and are often used to map a dictionary type with key/value pairs used for the properties of the entity type. For example, a `BuildMetadata` entity type can be defined without defining a dedicated CLR type: + + +[!code-csharp[BuildMetadata](../../../../samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs?name=BuildMetadata)] + +Notice that the shared-type entity type must be named - in this case, the name is `BuildMetadata`. These entity types are then accessed using a `DbSet` for the entity type which is obtained using the name. For example: + + +[!code-csharp[BuildMetadataSet](../../../../samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs?name=BuildMetadataSet)] + +This `DbSet` can be used to track entity instances: + +```csharp +await context.BuildMetadata.AddAsync( + new Dictionary + { + { "Tag", "v7.0.0-rc.1.22426.7" }, + { "Version", new Version(7, 0, 0) }, + { "Prerelease", true }, + { "Hash", "dc0f3e8ef10eb1464b27f0fd4704f53c01226036" } + }); +``` + +And execute queries: + + +[!code-csharp[BuildMetadataQuery](../../../../samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs?name=BuildMetadataQuery)] + +Now, in EF7, there is also an `Entry` method on `DbSet` which can be used to obtain state about an instance, _even if it is not yet tracked_. For example: + + +[!code-csharp[GetEntry](../../../../samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs?name=GetEntry)] + +### `ContextInitialized` is now logged as `Debug` + +In EF7, the event is logged at the level. For example: + +```output +dbug: 10/7/2022 12:27:52.379 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure) + Entity Framework Core 7.0.0-rc.2.22472.11 initialized 'BlogsContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:7.0.0-rc.2.22472.11' with options: SensitiveDataLoggingEnabled using NetTopologySuite +``` + +In previous releases it was logged at the level. For example: + +```output +info: 10/7/2022 12:30:34.757 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure) + Entity Framework Core 7.0.0-rc.2.22472.11 initialized 'BlogsContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:7.0.0-rc.2.22472.11' with options: SensitiveDataLoggingEnabled using NetTopologySuite +``` + +If desired, the log level can be changed back to `Information`: + + +[!code-csharp[ContextInitializedLog](../../../../samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs?name=ContextInitializedLog)] + +### `IEntityEntryGraphIterator` is publicly usable + +In EF7, the service can be used by applications. This is the service used internally when discovering a graph of entities to track, and also by . Here's an example that iterates over all entities reachable from some starting entity: + + +[!code-csharp[IEntityEntryGraphIterator](../../../../samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs?name=IEntityEntryGraphIterator)] + +Notice: + +- The iterator stops traversing from a given node when the callback delegate returns `false`. This example keeps track of visited entities and returns `false` when the entity has already been visited. This prevents infinite loops resulting from cycles in the graph. +- The `EntityEntryGraphNode` object allows state to be passed around without capturing it into the delegate. +- For every node visited other than the first, the node it was discovered from and the navigation is was discovered via are passed to the callback. diff --git a/samples/core/.editorconfig b/samples/core/.editorconfig index a241d5f867..dfe64dad4a 100644 --- a/samples/core/.editorconfig +++ b/samples/core/.editorconfig @@ -82,7 +82,7 @@ dotnet_style_null_propagation = true:suggestion # CSharp code style settings: [*.cs] # Modifier preferences -csharp_preferred_modifier_order = public,private,protected,internal,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +csharp_preferred_modifier_order = public, private, protected, internal, const, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion # Implicit and explicit types csharp_style_var_for_built_in_types = true:suggestion diff --git a/samples/core/Miscellaneous/NewInEFCore7/BlogsContext.cs b/samples/core/Miscellaneous/NewInEFCore7/BlogsContext.cs index c30385ff0d..e2cd029496 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/BlogsContext.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/BlogsContext.cs @@ -1,6 +1,5 @@ using System.Net; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NetTopologySuite.Geometries; namespace NewInEfCore7; @@ -185,8 +184,9 @@ protected BlogsContext(bool useSqlite = false) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => (UseSqlite ? optionsBuilder.UseSqlite(@$"DataSource={GetType().Name}") - : optionsBuilder.UseSqlServer(@$"Server=(localdb)\mssqllocaldb;Database={GetType().Name}", - sqlServerOptionsBuilder => sqlServerOptionsBuilder.UseNetTopologySuite())) + : optionsBuilder.UseSqlServer( + @$"Server=(localdb)\mssqllocaldb;Database={GetType().Name}", + sqlServerOptionsBuilder => sqlServerOptionsBuilder.UseNetTopologySuite())) .EnableSensitiveDataLogging() .LogTo( s => diff --git a/samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs b/samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs new file mode 100644 index 0000000000..1a80f563de --- /dev/null +++ b/samples/core/Miscellaneous/NewInEFCore7/DbContextApiSample.cs @@ -0,0 +1,228 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace NewInEfCore7; + +public static class DbContextApiSample +{ + public static async Task Find_siblings() + { + PrintSampleName(); + + await using var context = new BlogsContext(); + + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); + await context.Seed(); + context.ChangeTracker.Clear(); + + var blogs = await context.Blogs.Include(blog => blog.Posts).ToListAsync(); + var post = blogs.First().Posts.First(); + + #region UseFindSiblings + + Console.WriteLine($"Siblings to {post.Id}: '{post.Title}' are..."); + foreach (var sibling in context.FindSiblings(post, nameof(post.Blog))) + { + Console.WriteLine($" {sibling.Id}: '{sibling.Title}'"); + } + + #endregion + } + + public static async Task Get_entry_for_shared_type_entity_type() + { + PrintSampleName(); + + await using (var context = new BlogsContext { LoggingEnabled = true }) + { + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); + + var builds = new List> + { + new() + { + { "Tag", "v6.0.9" }, + { "Version", new Version(6, 0, 9) }, + { "Prerelease", false }, + { "Hash", "33e3c950af2eb996c0b3c48e30eb4471138da675" } + }, + new() + { + { "Tag", "v3.1.29" }, + { "Version", new Version(3, 1, 29) }, + { "Prerelease", false }, + { "Hash", "6e3b6bb213c8360c3a1e26f91c66234710535962" } + }, + new() + { + { "Tag", "v7.0.0-rc.1.22426.7" }, + { "Version", new Version(7, 0, 0) }, + { "Prerelease", true }, + { "Hash", "dc0f3e8ef10eb1464b27f0fd4704f53c01226036" } + } + }; + + ListBuilds(context, builds); + + await context.BuildMetadata.AddRangeAsync(builds); + ListBuilds(context, builds); + + await context.SaveChangesAsync(); + ListBuilds(context, builds); + } + + await using (var context = new BlogsContext { LoggingEnabled = true }) + { + #region BuildMetadataQuery + var builds = await context.BuildMetadata + .Where(metadata => !EF.Property(metadata, "Prerelease")) + .OrderBy(metadata => EF.Property(metadata, "Tag")) + .ToListAsync(); + #endregion + + ListBuilds(context, builds); + } + + void ListBuilds(BlogsContext context, List> builds) + { + Console.WriteLine(); + Console.WriteLine("Builds:"); + + foreach (var build in builds) + { + #region GetEntry + var state = context.BuildMetadata.Entry(build).State; + #endregion + + Console.WriteLine( + $" Build {build["Tag"]} for {build["Version"]} with hash '{build["Hash"]}' with state '{state}'"); + } + + Console.WriteLine(); + } + } + + public static async Task Use_IEntityEntryGraphIterator() + { + PrintSampleName(); + + await using var context = new BlogsContext(); + + await context.Database.EnsureDeletedAsync(); + await context.Database.EnsureCreatedAsync(); + await context.Seed(); + + #region IEntityEntryGraphIterator + var blogEntry = context.ChangeTracker.Entries().First(); + var found = new HashSet(); + var iterator = context.GetService(); + iterator.TraverseGraph(new EntityEntryGraphNode>(blogEntry, found, null, null), node => + { + if (node.NodeState.Contains(node.Entry.Entity)) + { + return false; + } + + Console.Write($"Found with '{node.Entry.Entity.GetType().Name}'"); + + if (node.InboundNavigation != null) + { + Console.Write($" by traversing '{node.InboundNavigation.Name}' from '{node.SourceEntry!.Entity.GetType().Name}'"); + } + + Console.WriteLine(); + + node.NodeState.Add(node.Entry.Entity); + + return true; + }); + + Console.WriteLine(); + Console.WriteLine($"Finished iterating. Found {found.Count} entities."); + Console.WriteLine(); + #endregion + } + + private static void PrintSampleName([CallerMemberName] string? methodName = null) + { + Console.WriteLine($">>>> Sample: {methodName}"); + Console.WriteLine(); + } + + public class BlogsContext : ModelBuildingBlogsContextBase + { + #region BuildMetadataSet + public DbSet> BuildMetadata + => Set>("BuildMetadata"); + #endregion + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + #region ContextInitializedLog + optionsBuilder.ConfigureWarnings( + builder => + { + builder.Log((CoreEventId.ContextInitialized, LogLevel.Information)); + }); + #endregion + + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + #region BuildMetadata + modelBuilder.SharedTypeEntity>( + "BuildMetadata", b => + { + b.IndexerProperty("Id"); + b.IndexerProperty("Tag"); + b.IndexerProperty("Version"); + b.IndexerProperty("Hash"); + b.IndexerProperty("Prerelease"); + }); + #endregion + } + + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + base.ConfigureConventions(configurationBuilder); + + configurationBuilder.Properties().HaveConversion(); + } + + private class VersionConverter : ValueConverter + { + public VersionConverter() + : base(v => v.ToString(), v => new Version(v)) + { + } + } + } +} + +public static class BlogsContextExtensions +{ + #region FindSiblings + public static IEnumerable FindSiblings( + this DbContext context, TEntity entity, string navigationToParent) + where TEntity : class + { + var parentEntry = context.Entry(entity).Reference(navigationToParent); + + return context.Entry(parentEntry.CurrentValue!) + .Collection(parentEntry.Metadata.Inverse!) + .CurrentValue! + .OfType() + .Where(e => !ReferenceEquals(e, entity)); + } + #endregion +} diff --git a/samples/core/Miscellaneous/NewInEFCore7/DocumentsContext.cs b/samples/core/Miscellaneous/NewInEFCore7/DocumentsContext.cs index 53f6e76376..5fda832e45 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/DocumentsContext.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/DocumentsContext.cs @@ -201,7 +201,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }); } }); - }); }); } diff --git a/samples/core/Miscellaneous/NewInEFCore7/GroupByEntityTypeSample.cs b/samples/core/Miscellaneous/NewInEFCore7/GroupByEntityTypeSample.cs index 26b94808db..31f9b4c48e 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/GroupByEntityTypeSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/GroupByEntityTypeSample.cs @@ -44,11 +44,9 @@ await context.AddRangeAsync( await using (var context = new TContext()) { #region GroupByEntityType - var query = context.Books .GroupBy(s => s.Author) .Select(s => new { Author = s.Key, MaxPrice = s.Max(p => p.Price) }); - #endregion await foreach (var group in query.AsAsyncEnumerable()) diff --git a/samples/core/Miscellaneous/NewInEFCore7/InjectLoggerSample.cs b/samples/core/Miscellaneous/NewInEFCore7/InjectLoggerSample.cs index 90458f83d2..8c98e118b7 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/InjectLoggerSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/InjectLoggerSample.cs @@ -1,9 +1,5 @@ -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace NewInEfCore7; diff --git a/samples/core/Miscellaneous/NewInEFCore7/JsonColumnsSample.cs b/samples/core/Miscellaneous/NewInEFCore7/JsonColumnsSample.cs index 083211d2c3..44f6c3e60e 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/JsonColumnsSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/JsonColumnsSample.cs @@ -95,10 +95,7 @@ private static async Task JsonColumnsTest() .Select( post => new { - post.Author!.Name, - post.Metadata!.Views, - Searches = post.Metadata.TopSearches, - Commits = post.Metadata.Updates + post.Author!.Name, post.Metadata!.Views, Searches = post.Metadata.TopSearches, Commits = post.Metadata.Updates }) .ToListAsync(); #endregion @@ -106,7 +103,8 @@ private static async Task JsonColumnsTest() Console.WriteLine(); foreach (var postWithViews in postsWithViews) { - Console.WriteLine($"Post by {postWithViews.Name} with {postWithViews.Views} views had {postWithViews.Commits.Count} commits with {postWithViews.Searches.Sum(term => term.Count)} searches"); + Console.WriteLine( + $"Post by {postWithViews.Name} with {postWithViews.Views} views had {postWithViews.Commits.Count} commits with {postWithViews.Searches.Sum(term => term.Count)} searches"); } Console.WriteLine(); @@ -140,7 +138,7 @@ private static async Task JsonColumnsTest() context.ChangeTracker.Clear(); Console.WriteLine(); - Console.WriteLine($"Updating only 'Country' in a 'Contact' JSON document..."); + Console.WriteLine("Updating only 'Country' in a 'Contact' JSON document..."); Console.WriteLine(); #region UpdateProperty @@ -240,10 +238,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) author => author.Contact, ownedNavigationBuilder => { ownedNavigationBuilder.ToTable("Contacts"); - ownedNavigationBuilder.OwnsOne(contactDetails => contactDetails.Address, ownedOwnedNavigationBuilder => - { - ownedOwnedNavigationBuilder.ToTable("Addresses"); - }); + ownedNavigationBuilder.OwnsOne( + contactDetails => contactDetails.Address, ownedOwnedNavigationBuilder => + { + ownedOwnedNavigationBuilder.ToTable("Addresses"); + }); }); } #endregion diff --git a/samples/core/Miscellaneous/NewInEFCore7/LazyConnectionStringSample.cs b/samples/core/Miscellaneous/NewInEFCore7/LazyConnectionStringSample.cs index 147d212bac..e2a8d2dd95 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/LazyConnectionStringSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/LazyConnectionStringSample.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; namespace NewInEfCore7; diff --git a/samples/core/Miscellaneous/NewInEFCore7/MiscellaneousTranslationsSample.cs b/samples/core/Miscellaneous/NewInEFCore7/MiscellaneousTranslationsSample.cs index dc73652641..6e4f5fd636 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/MiscellaneousTranslationsSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/MiscellaneousTranslationsSample.cs @@ -61,12 +61,13 @@ public static async Task Translate_string_IndexOf() { #region AtTimeZone var query = context.Posts - .Select(post => new - { - post.Title, - PacificTime = EF.Functions.AtTimeZone(post.PublishedOn, "Pacific Standard Time"), - UkTime = EF.Functions.AtTimeZone(post.PublishedOn, "GMT Standard Time"), - }); + .Select( + post => new + { + post.Title, + PacificTime = EF.Functions.AtTimeZone(post.PublishedOn, "Pacific Standard Time"), + UkTime = EF.Functions.AtTimeZone(post.PublishedOn, "GMT Standard Time"), + }); #endregion await foreach (var post in query.AsAsyncEnumerable()) diff --git a/samples/core/Miscellaneous/NewInEFCore7/ModelBuildingConventionsSample.cs b/samples/core/Miscellaneous/NewInEFCore7/ModelBuildingConventionsSample.cs index 2e336359e4..acf4fcf933 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/ModelBuildingConventionsSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/ModelBuildingConventionsSample.cs @@ -1,11 +1,8 @@ -using System.Net; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + #pragma warning disable CS0169 namespace NewInEfCore7; @@ -154,7 +151,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { - configurationBuilder.Conventions.Add(_ => new DiscriminatorLengthConvention3()); + configurationBuilder.Conventions.Add(_ => new DiscriminatorLengthConvention3()); base.ConfigureConventions(configurationBuilder); } @@ -233,8 +230,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { - configurationBuilder.Conventions.Add(_ => new MaxStringLengthConvention()); - configurationBuilder.Conventions.Add(_ => new DiscriminatorLengthConvention3()); + configurationBuilder.Conventions.Add(_ => new MaxStringLengthConvention()); + configurationBuilder.Conventions.Add(_ => new DiscriminatorLengthConvention3()); base.ConfigureConventions(configurationBuilder); } @@ -284,7 +281,8 @@ public sealed class PersistAttribute : Attribute #region LaundryBasket public class LaundryBasket { - [Persist] [Key] + [Persist] + [Key] private readonly int _id; [Persist] diff --git a/samples/core/Miscellaneous/NewInEFCore7/NewInEFCore7.csproj b/samples/core/Miscellaneous/NewInEFCore7/NewInEFCore7.csproj index f5c0341e90..f63454813e 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/NewInEFCore7.csproj +++ b/samples/core/Miscellaneous/NewInEFCore7/NewInEFCore7.csproj @@ -9,25 +9,25 @@ - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + diff --git a/samples/core/Miscellaneous/NewInEFCore7/OptimisticConcurrencyInterceptionSample.cs b/samples/core/Miscellaneous/NewInEFCore7/OptimisticConcurrencyInterceptionSample.cs index f15501c5a4..901e54ad69 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/OptimisticConcurrencyInterceptionSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/OptimisticConcurrencyInterceptionSample.cs @@ -1,6 +1,4 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace NewInEfCore7; diff --git a/samples/core/Miscellaneous/NewInEFCore7/Program.cs b/samples/core/Miscellaneous/NewInEFCore7/Program.cs index a171372914..cce8d1121a 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/Program.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/Program.cs @@ -67,5 +67,9 @@ public static async Task Main() await SaveChangesPerformanceSample.SaveChanges_SQL_generation_samples_SqlServer(); await SaveChangesPerformanceSample.SaveChanges_SQL_generation_samples_Sqlite(); + + await DbContextApiSample.Find_siblings(); + await DbContextApiSample.Get_entry_for_shared_type_entity_type(); + await DbContextApiSample.Use_IEntityEntryGraphIterator(); } } diff --git a/samples/core/Miscellaneous/NewInEFCore7/QueryInterceptionSample.cs b/samples/core/Miscellaneous/NewInEFCore7/QueryInterceptionSample.cs index 42b5af3d9a..77e202bcd6 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/QueryInterceptionSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/QueryInterceptionSample.cs @@ -1,8 +1,4 @@ -using System.Linq.Expressions; -using System.Reflection; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace NewInEfCore7; diff --git a/samples/core/Miscellaneous/NewInEFCore7/SaveChangesPerformanceSample.cs b/samples/core/Miscellaneous/NewInEFCore7/SaveChangesPerformanceSample.cs index e9c5a63991..467a0ce693 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/SaveChangesPerformanceSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/SaveChangesPerformanceSample.cs @@ -57,6 +57,7 @@ ON BlogsWithTriggers { await context.AddAsync(new Blog { Name = "Foo" + i }); } + await context.SaveChangesAsync(); #endregion } @@ -73,21 +74,15 @@ ON BlogsWithTriggers { await context.AddAsync(new BlogWithTrigger { Name = "Foo" + i }); } + await context.SaveChangesAsync(); } await using (var context = new TContext()) { #region InsertGraph - await context.AddAsync(new Blog - { - Name = "MyBlog", - Posts = - { - new() { Title = "My first post" }, - new() { Title = "My second post" } - } - }); + await context.AddAsync( + new Blog { Name = "MyBlog", Posts = { new() { Title = "My first post" }, new() { Title = "My second post" } } }); await context.SaveChangesAsync(); #endregion } @@ -95,15 +90,8 @@ await context.AddAsync(new Blog await using (var context = new TContext()) { #region InsertGraph - await context.AddAsync(new Blog - { - Name = "MyBlog", - Posts = - { - new() { Title = "My first post" }, - new() { Title = "My second post" } - } - }); + await context.AddAsync( + new Blog { Name = "MyBlog", Posts = { new() { Title = "My first post" }, new() { Title = "My second post" } } }); await context.SaveChangesAsync(); #endregion } diff --git a/samples/core/Miscellaneous/NewInEFCore7/SimpleMaterializationSample.cs b/samples/core/Miscellaneous/NewInEFCore7/SimpleMaterializationSample.cs index ab2efba43e..fb1f8b2e82 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/SimpleMaterializationSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/SimpleMaterializationSample.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace NewInEfCore7; diff --git a/samples/core/Miscellaneous/NewInEFCore7/SpatialAggregateFunctionsSample.cs b/samples/core/Miscellaneous/NewInEFCore7/SpatialAggregateFunctionsSample.cs index 3cadd0f408..77732fb353 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/SpatialAggregateFunctionsSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/SpatialAggregateFunctionsSample.cs @@ -1,3 +1,4 @@ +using NetTopologySuite.Algorithm; using NetTopologySuite.Geometries; using NetTopologySuite.Geometries.Utilities; using NetTopologySuite.Operation.Union; @@ -33,10 +34,21 @@ private static async Task QueryTest() await context.Database.EnsureCreatedAsync(); await context.AddRangeAsync( - new GeoCache { Name = "Sandpiper Cache", Owner = "Nilwob Inc.", Location = new Point(-93.71855, 41.760783) { SRID = 4326 } }, - new GeoCache { Name = "Paddy's Pot-O-Gold", Owner = "Nilwob Inc.", Location = new Point(-93.733633, 41.775633) { SRID = 4326 } }, + new GeoCache + { + Name = "Sandpiper Cache", Owner = "Nilwob Inc.", Location = new Point(-93.71855, 41.760783) { SRID = 4326 } + }, + new GeoCache + { + Name = "Paddy's Pot-O-Gold", Owner = "Nilwob Inc.", Location = new Point(-93.733633, 41.775633) { SRID = 4326 } + }, new GeoCache { Name = "Blazing Beacon", Owner = "The Spokes", Location = new Point(-1.606483, 55.392433) { SRID = 4326 } }, - new GeoCache { Name = "133 Steps to Relaxation", Owner = "Team isuforester", Location = new Point(-93.854733, 41.9389) { SRID = 4326 } }, + new GeoCache + { + Name = "133 Steps to Relaxation", + Owner = "Team isuforester", + Location = new Point(-93.854733, 41.9389) { SRID = 4326 } + }, new EuclideanPoint { Name = "A", Point = new Point(1.0, 1.0) }, new EuclideanPoint { Name = "A", Point = new Point(1.0, 2.0) }, new EuclideanPoint { Name = "B", Point = new Point(2.0, 1.0) }, @@ -51,11 +63,8 @@ await context.AddRangeAsync( var query = context.Caches .Where(cache => cache.Location.X < -90) .GroupBy(cache => cache.Owner) - .Select(grouping => new - { - Id = grouping.Key, - Combined = GeometryCombiner.Combine(grouping.Select(cache => cache.Location)) - }); + .Select( + grouping => new { Id = grouping.Key, Combined = GeometryCombiner.Combine(grouping.Select(cache => cache.Location)) }); #endregion await foreach (var group in query.AsAsyncEnumerable()) @@ -69,11 +78,7 @@ await context.AddRangeAsync( var query = context.Caches .Where(cache => cache.Location.X < -90) .GroupBy(cache => cache.Owner) - .Select(grouping => new - { - Id = grouping.Key, - ConvexHull = NetTopologySuite.Algorithm.ConvexHull.Create(grouping.Select(cache => cache.Location)) - }); + .Select(grouping => new { Id = grouping.Key, ConvexHull = ConvexHull.Create(grouping.Select(cache => cache.Location)) }); await foreach (var group in query.AsAsyncEnumerable()) { @@ -86,11 +91,7 @@ await context.AddRangeAsync( var query = context.Caches .Where(cache => cache.Location.X < -90) .GroupBy(cache => cache.Owner) - .Select(grouping => new - { - Id = grouping.Key, - Union = UnaryUnionOp.Union(grouping.Select(cache => cache.Location)) - }); + .Select(grouping => new { Id = grouping.Key, Union = UnaryUnionOp.Union(grouping.Select(cache => cache.Location)) }); await foreach (var group in query.AsAsyncEnumerable()) { @@ -102,11 +103,11 @@ await context.AddRangeAsync( { var query = context.Points .GroupBy(point => point.Name) - .Select(grouping => new - { - Id = grouping.Key, - Combined = EnvelopeCombiner.CombineAsGeometry(grouping.Select(point => point.Point)) - }); + .Select( + grouping => new + { + Id = grouping.Key, Combined = EnvelopeCombiner.CombineAsGeometry(grouping.Select(point => point.Point)) + }); await foreach (var group in query.AsAsyncEnumerable()) { diff --git a/samples/core/Miscellaneous/NewInEFCore7/StatisticalAggregateFunctionsSample.cs b/samples/core/Miscellaneous/NewInEFCore7/StatisticalAggregateFunctionsSample.cs index 6d8c8d0899..4ff3b2139c 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/StatisticalAggregateFunctionsSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/StatisticalAggregateFunctionsSample.cs @@ -1,6 +1,3 @@ -using NetTopologySuite.Geometries.Utilities; -using NetTopologySuite.Operation.Union; - namespace NewInEfCore7; public static class StatisticalAggregateFunctionsSample diff --git a/samples/core/Miscellaneous/NewInEFCore7/StoredProcedureMappingSample.cs b/samples/core/Miscellaneous/NewInEFCore7/StoredProcedureMappingSample.cs index 9a390909f7..a70bce9403 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/StoredProcedureMappingSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/StoredProcedureMappingSample.cs @@ -98,7 +98,6 @@ await context.Documents await context.SaveChangesAsync(); await context2.SaveChangesAsync(); - } catch (DbUpdateConcurrencyException exception) { @@ -118,7 +117,6 @@ await context.Documents await context.SaveChangesAsync(); await context2.SaveChangesAsync(); - } catch (DbUpdateConcurrencyException exception) { @@ -138,7 +136,6 @@ await context.Documents await context.SaveChangesAsync(); await context2.SaveChangesAsync(); - } catch (DbUpdateConcurrencyException exception) { diff --git a/samples/core/Miscellaneous/NewInEFCore7/StringAggregateFunctionsSample.cs b/samples/core/Miscellaneous/NewInEFCore7/StringAggregateFunctionsSample.cs index 6e5012a981..f7016ea4a5 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/StringAggregateFunctionsSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/StringAggregateFunctionsSample.cs @@ -15,11 +15,9 @@ public static async Task Translate_string_Concat_and_string_Join() context.LoggingEnabled = true; #region Join - var query = context.Posts .GroupBy(post => post.Author) .Select(grouping => new { Author = grouping.Key, Books = string.Join("|", grouping.Select(post => post.Title)) }); - #endregion await foreach (var author in query.AsAsyncEnumerable()) @@ -33,7 +31,6 @@ public static async Task Translate_string_Concat_and_string_Join() await using (var context = new BlogsContext { LoggingEnabled = true }) { #region ConcatAndJoin - var query = context.Posts .GroupBy(post => post.Author!.Name) .Select( @@ -52,7 +49,6 @@ public static async Task Translate_string_Concat_and_string_Join() .Where(post => post.Content.Length >= 10) .Select(post => "'" + post.Content.Substring(0, 10) + "' ")) }); - #endregion await foreach (var author in query.AsAsyncEnumerable())