From a1a0de8303a184e265a5d482987062ed3a158140 Mon Sep 17 00:00:00 2001 From: Jaben Cargman Date: Wed, 3 Dec 2025 18:07:31 -0500 Subject: [PATCH] Upgrade to .NET 10 and EF Core 10 - Update TargetFramework to net10.0 - Update Microsoft.EntityFrameworkCore packages to 10.0.0 - Update GitHub Actions workflow to use .NET 10.x - Update README for EF Core 10, add v9 to previous versions - Document EF Core 10 behavior change for deleted parent with added children - Update test to reflect EF Core 10 relaxed validation behavior - Remove try/catch for exception no longer thrown in EF Core 10 --- .github/workflows/deploy.yml | 2 +- Directory.Build.props | 4 ++-- README.md | 14 ++++++++++++-- .../NorthwindDbContextTests.cs | 17 ++++++++++++++--- .../TrackableEntities.EF.Core.Tests.csproj | 4 ++-- .../DbContextExtensions.cs | 10 +--------- .../TrackableEntities.EF.Core.csproj | 2 +- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ab71f94..ae2af29 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dotnet-version: [9.x] + dotnet-version: [10.x] services: mssql: image: mcr.microsoft.com/mssql/server:2019-latest diff --git a/Directory.Build.props b/Directory.Build.props index 8ca5004..afe9b4b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - net9.0 + net10.0 Tony Sneed Tony Sneed $(NoWarn);CS1591 @@ -12,7 +12,7 @@ True README.md - https://github.com/TrackableEntities/TrackableEntities.Core/releases/tag/v9.0.0 + https://github.com/TrackableEntities/TrackableEntities.Core/releases/tag/v10.0.0 diff --git a/README.md b/README.md index 635c07d..e559ebd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Change-tracking across service boundaries with [ASP.NET Core](https://docs.micro ## Installation -Trackable Entities for EF Core 9 is available as a NuGet package that can be installed in an ASP.NET Core Web API project that uses Entity Framework Core. +Trackable Entities for EF Core 10 is available as a NuGet package that can be installed in an ASP.NET Core Web API project that uses Entity Framework Core. You can use the [Package Manager UI or Console](https://docs.microsoft.com/en-us/nuget/tools/package-manager-console) in Visual Studio to install the TE package. @@ -28,7 +28,17 @@ dotnet add package TrackableEntities.EF.Core ## Packages for Previous Versions of EntityFramework Core -##### [EntityFramework Core v8](https://www.nuget.org/packages/TrackableEntities.EF.Core/8.0.0) | [EntityFramework Core v7](https://www.nuget.org/packages/TrackableEntities.EF.Core/7.0.0) | [EntityFramework Core v6](https://www.nuget.org/packages/TrackableEntities.EF.Core/6.0.0) | [EntityFramework Core v5](https://www.nuget.org/packages/TrackableEntities.EF.Core/5.0.1) | [EntityFramework Core v3](https://www.nuget.org/packages/TrackableEntities.EF.Core/3.1.1) +##### [EntityFramework Core v9](https://www.nuget.org/packages/TrackableEntities.EF.Core/9.0.1) | [EntityFramework Core v8](https://www.nuget.org/packages/TrackableEntities.EF.Core/8.0.0) | [EntityFramework Core v7](https://www.nuget.org/packages/TrackableEntities.EF.Core/7.0.0) | [EntityFramework Core v6](https://www.nuget.org/packages/TrackableEntities.EF.Core/6.0.0) | [EntityFramework Core v5](https://www.nuget.org/packages/TrackableEntities.EF.Core/5.0.1) | [EntityFramework Core v3](https://www.nuget.org/packages/TrackableEntities.EF.Core/3.1.1) + +## EF Core 10 Behavior Changes + +EF Core 10 includes a change in entity state validation that affects how Trackable Entities handles certain edge cases: + +- **Deleted parent with Added children**: In EF Core 9 and earlier, attempting to delete an entity that had children marked as `Added` would throw an `InvalidOperationException`. EF Core 10 relaxes this validation and allows the operation to proceed. + +- **Impact**: If you previously relied on receiving an exception when accidentally marking a parent as `Deleted` while its children were marked as `Added`, this validation is no longer enforced by EF Core. The operation will now proceed, and the child entities will be set to `Deleted` along with their parent. + +- **Recommendation**: Ensure your client-side logic properly validates entity state combinations before sending object graphs to the server. Avoid marking parent entities as `Deleted` when child entities are marked as `Added`, as this represents a logical contradiction (you cannot delete a parent while simultaneously adding new children to it). ## Trackable Entities Interfaces diff --git a/TrackableEntities.EF.Core.Tests/NorthwindDbContextTests.cs b/TrackableEntities.EF.Core.Tests/NorthwindDbContextTests.cs index 3cc5d88..83b43c4 100644 --- a/TrackableEntities.EF.Core.Tests/NorthwindDbContextTests.cs +++ b/TrackableEntities.EF.Core.Tests/NorthwindDbContextTests.cs @@ -850,13 +850,24 @@ public void Apply_Changes_Should_Mark_Unchanged_Order_Deleted_Customer_With_Addr Customer = order.Customer }; order.Customer.CustomerAddresses = new List { address1, address2 }; + address1.TrackingState = TrackingState.Added; + address2.TrackingState = TrackingState.Added; order.Customer.TrackingState = TrackingState.Deleted; - // Act / Assert - Exception ex = Assert.Throws(() => context.ApplyChanges(order)); + // Act + // Note: When navigating from Order (many side) to Customer (one side), the Customer's + // Deleted state is overridden to Unchanged since the Customer may be related to other entities. + // However, the Customer's TrackingState property is still Deleted, so when processing + // child entities (CustomerAddresses), they are set to Deleted based on the parent's TrackingState. + context.ApplyChanges(order); // Assert - Assert.Equal(Constants.ExceptionMessages.DeletedWithAddedChildren, ex.Message); + Assert.Equal(EntityState.Unchanged, context.Entry(order).State); + // Customer is set to Unchanged because it's reached via OneToMany navigation from Order + Assert.Equal(EntityState.Unchanged, context.Entry(order.Customer).State); + // Addresses are set to Deleted because parent's TrackingState is Deleted (even though parent EntityState is Unchanged) + Assert.Equal(EntityState.Deleted, context.Entry(address1).State); + Assert.Equal(EntityState.Deleted, context.Entry(address2).State); } [Fact] diff --git a/TrackableEntities.EF.Core.Tests/TrackableEntities.EF.Core.Tests.csproj b/TrackableEntities.EF.Core.Tests/TrackableEntities.EF.Core.Tests.csproj index 6922b8a..073c283 100644 --- a/TrackableEntities.EF.Core.Tests/TrackableEntities.EF.Core.Tests.csproj +++ b/TrackableEntities.EF.Core.Tests/TrackableEntities.EF.Core.Tests.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/TrackableEntities.EF.Core/DbContextExtensions.cs b/TrackableEntities.EF.Core/DbContextExtensions.cs index 7215cf4..b107a93 100644 --- a/TrackableEntities.EF.Core/DbContextExtensions.cs +++ b/TrackableEntities.EF.Core/DbContextExtensions.cs @@ -66,15 +66,7 @@ public static void ApplyChanges(this DbContext context, ITrackable item) if (node.SourceEntry.State == EntityState.Deleted || parent?.TrackingState == TrackingState.Deleted) { - try - { - // Will throw if there are added children - SetEntityState(node.Entry, TrackingState.Deleted.ToEntityState(), trackable); - } - catch (InvalidOperationException e) - { - throw new InvalidOperationException(Constants.ExceptionMessages.DeletedWithAddedChildren, e); - } + SetEntityState(node.Entry, TrackingState.Deleted.ToEntityState(), trackable); return; } break; diff --git a/TrackableEntities.EF.Core/TrackableEntities.EF.Core.csproj b/TrackableEntities.EF.Core/TrackableEntities.EF.Core.csproj index 9270c95..b32caf9 100644 --- a/TrackableEntities.EF.Core/TrackableEntities.EF.Core.csproj +++ b/TrackableEntities.EF.Core/TrackableEntities.EF.Core.csproj @@ -8,7 +8,7 @@ - +