From 9d469a02710cfa5009f7bba353c1a1ef2d5d588e Mon Sep 17 00:00:00 2001 From: Henk Kin Date: Fri, 7 Feb 2020 11:28:59 +0100 Subject: [PATCH] Due to change in changetracking behavior for ChangeTracker.CascadeDeleteTiming and ChangeTracker.DeleteOrphansTiming to default value Immediate. In post- and preprocessing of SaveChange, some logic is done, but the order of entities is not preserved. Thats we will change this to OnSaveChanges, to allow Eventual Consistency. --- ...lient.EntityFrameworkCore.SqlServer.csproj | 2 +- .../SqlServerDbContext.cs | 170 +++++++++++++----- .../UnitOfWorkPartForSqlServerDbContext.cs | 19 +- 3 files changed, 125 insertions(+), 66 deletions(-) diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClient.EntityFrameworkCore.SqlServer.csproj b/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClient.EntityFrameworkCore.SqlServer.csproj index b364594..c5b102e 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClient.EntityFrameworkCore.SqlServer.csproj +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/DataAccessClient.EntityFrameworkCore.SqlServer.csproj @@ -13,7 +13,7 @@ Henk Kin EntityFrameworkCore, EF, SqlServer, DependencyInjection, Multiple DbContext support, DataAccess, Repository, UnitOfWork, SoftDelete, Translation, RowVersioning, Filtering, Paging, Sorting, Includes - 0.0.2 + 0.0.3 diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs index 719022c..6b8e070 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs @@ -7,6 +7,7 @@ using DataAccessClient.Exceptions; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Infrastructure; namespace DataAccessClient.EntityFrameworkCore.SqlServer @@ -22,19 +23,29 @@ protected SqlServerDbContext(DbContextOptions options) } // for testing purpose - protected internal SqlServerDbContext() : base() { } + protected internal SqlServerDbContext() : base() + { + } protected override void OnModelCreating(ModelBuilder modelBuilder) { - var configureEntityBehaviorIIdentifiable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorIIdentifiable)); - var configureEntityBehaviorICreatable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorICreatable)); - var configureEntityBehaviorIModifiable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorIModifiable)); - var configureEntityBehaviorISoftDeletable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorISoftDeletable)); - var configureEntityBehaviorIRowVersionable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorIRowVersionable)); - var configureEntityBehaviorITranslatable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorITranslatable)); - var configureEntityBehaviorTranslatedProperties = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods.Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorTranslatedProperties)); + var configureEntityBehaviorIIdentifiable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods + .Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorIIdentifiable)); + var configureEntityBehaviorICreatable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods + .Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorICreatable)); + var configureEntityBehaviorIModifiable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods + .Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorIModifiable)); + var configureEntityBehaviorISoftDeletable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods + .Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorISoftDeletable)); + var configureEntityBehaviorIRowVersionable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods + .Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorIRowVersionable)); + var configureEntityBehaviorITranslatable = typeof(ModelBuilderExtensions).GetTypeInfo().DeclaredMethods + .Single(m => m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorITranslatable)); + var configureEntityBehaviorTranslatedProperties = typeof(ModelBuilderExtensions).GetTypeInfo() + .DeclaredMethods.Single(m => + m.Name == nameof(ModelBuilderExtensions.ConfigureEntityBehaviorTranslatedProperties)); - var args = new object[] { modelBuilder }; + var args = new object[] {modelBuilder}; var ignoredEntityTypes = new[] { @@ -43,34 +54,44 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }; var entityTypes = modelBuilder.Model.GetEntityTypes() - .Where(p=> !ignoredEntityTypes.Contains(p.ClrType)).ToList(); + .Where(p => !ignoredEntityTypes.Contains(p.ClrType)).ToList(); foreach (var entityType in entityTypes) { var entityInterfaces = entityType.ClrType.GetInterfaces(); - if (entityInterfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IIdentifiable<>))) + if (entityInterfaces.Any( + x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IIdentifiable<>))) { - var identifierType = entityType.ClrType.GetInterface(typeof(IIdentifiable<>).Name).GenericTypeArguments[0]; - configureEntityBehaviorIIdentifiable.MakeGenericMethod(entityType.ClrType, identifierType).Invoke(null, args); + var identifierType = entityType.ClrType.GetInterface(typeof(IIdentifiable<>).Name) + .GenericTypeArguments[0]; + configureEntityBehaviorIIdentifiable.MakeGenericMethod(entityType.ClrType, identifierType) + .Invoke(null, args); } if (entityInterfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICreatable<>))) { - var identifierType = entityType.ClrType.GetInterface(typeof(ICreatable<>).Name).GenericTypeArguments[0]; - configureEntityBehaviorICreatable.MakeGenericMethod(entityType.ClrType, identifierType).Invoke(null, args); + var identifierType = entityType.ClrType.GetInterface(typeof(ICreatable<>).Name) + .GenericTypeArguments[0]; + configureEntityBehaviorICreatable.MakeGenericMethod(entityType.ClrType, identifierType) + .Invoke(null, args); } if (entityInterfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IModifiable<>))) { - var identifierType = entityType.ClrType.GetInterface(typeof(IModifiable<>).Name).GenericTypeArguments[0]; - configureEntityBehaviorIModifiable.MakeGenericMethod(entityType.ClrType, identifierType).Invoke(null, args); + var identifierType = entityType.ClrType.GetInterface(typeof(IModifiable<>).Name) + .GenericTypeArguments[0]; + configureEntityBehaviorIModifiable.MakeGenericMethod(entityType.ClrType, identifierType) + .Invoke(null, args); } - if (entityInterfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ISoftDeletable<>))) + if (entityInterfaces.Any(x => + x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ISoftDeletable<>))) { - var identifierType = entityType.ClrType.GetInterface(typeof(ISoftDeletable<>).Name).GenericTypeArguments[0]; - configureEntityBehaviorISoftDeletable.MakeGenericMethod(entityType.ClrType, identifierType).Invoke(null, args); + var identifierType = entityType.ClrType.GetInterface(typeof(ISoftDeletable<>).Name) + .GenericTypeArguments[0]; + configureEntityBehaviorISoftDeletable.MakeGenericMethod(entityType.ClrType, identifierType) + .Invoke(null, args); } if (entityInterfaces.Any(x => !x.IsGenericType && x == typeof(IRowVersionable))) @@ -78,12 +99,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) configureEntityBehaviorIRowVersionable.MakeGenericMethod(entityType.ClrType).Invoke(null, args); } - if (entityInterfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ITranslatable<,>))) + if (entityInterfaces.Any(x => + x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ITranslatable<,>))) { - var entityTranslationType = entityType.ClrType.GetInterface(typeof(ITranslatable<,>).Name).GenericTypeArguments[0]; - var identifierType = entityType.ClrType.GetInterface(typeof(ITranslatable<,>).Name).GenericTypeArguments[1]; + var entityTranslationType = entityType.ClrType.GetInterface(typeof(ITranslatable<,>).Name) + .GenericTypeArguments[0]; + var identifierType = entityType.ClrType.GetInterface(typeof(ITranslatable<,>).Name) + .GenericTypeArguments[1]; - configureEntityBehaviorITranslatable.MakeGenericMethod(entityType.ClrType, entityTranslationType, identifierType).Invoke(null, args); + configureEntityBehaviorITranslatable + .MakeGenericMethod(entityType.ClrType, entityTranslationType, identifierType) + .Invoke(null, args); } configureEntityBehaviorTranslatedProperties.MakeGenericMethod(entityType.ClrType).Invoke(null, args); @@ -128,11 +154,13 @@ public override int SaveChanges(bool acceptAllChangesOnSuccess) } } + throw; } } - public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken()) + public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, + CancellationToken cancellationToken = new CancellationToken()) { var authenticatedUser = await _authenticatedUserIdentifierProvider.ExecuteAsync(); @@ -174,36 +202,84 @@ public override int SaveChanges(bool acceptAllChangesOnSuccess) private void AdjustEntities(TIdentifierType authenticatedUserIdentifier) { - foreach (var entityEntry in ChangeTracker.Entries()) + var originalCascadeDeleteTiming = ChangeTracker.CascadeDeleteTiming; + var originalDeleteOrphansTiming = ChangeTracker.DeleteOrphansTiming; + try { - var rowVersionProperty = entityEntry.Property(u => u.RowVersion); - var rowVersion = rowVersionProperty.CurrentValue; - //https://github.com/aspnet/EntityFramework/issues/4512 - rowVersionProperty.OriginalValue = rowVersion; - } + ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; + ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + foreach (var entityEntry in ChangeTracker.Entries()) + { + var rowVersionProperty = entityEntry.Property(u => u.RowVersion); + var rowVersion = rowVersionProperty.CurrentValue; + //https://github.com/aspnet/EntityFramework/issues/4512 + rowVersionProperty.OriginalValue = rowVersion; + } + + foreach (var entityEntry in ChangeTracker.Entries>() + .Where(c => c.State == EntityState.Deleted)) + { + entityEntry.State = EntityState.Unchanged; + + entityEntry.Entity.IsDeleted = true; + entityEntry.Entity.DeletedById = authenticatedUserIdentifier; + entityEntry.Entity.DeletedOn = DateTime.UtcNow; + entityEntry.Member(nameof(ISoftDeletable.IsDeleted)).IsModified = true; + entityEntry.Member(nameof(ISoftDeletable.DeletedById)).IsModified = true; + entityEntry.Member(nameof(ISoftDeletable.DeletedOn)).IsModified = true; + } + + foreach (var entityEntry in ChangeTracker.Entries>() + .Where(c => c.State == EntityState.Added)) + { + entityEntry.Entity.CreatedById = authenticatedUserIdentifier; + entityEntry.Entity.CreatedOn = DateTime.UtcNow; + } - foreach (var entityEntry in ChangeTracker.Entries>().Where(c => c.State == EntityState.Deleted)) + foreach (var entityEntry in ChangeTracker.Entries>() + .Where(c => c.State == EntityState.Modified)) + { + entityEntry.Entity.ModifiedById = authenticatedUserIdentifier; + entityEntry.Entity.ModifiedOn = DateTime.UtcNow; + } + } + finally { - entityEntry.State = EntityState.Unchanged; - - entityEntry.Entity.IsDeleted = true; - entityEntry.Entity.DeletedById = authenticatedUserIdentifier; - entityEntry.Entity.DeletedOn = DateTime.UtcNow; - entityEntry.Member(nameof(ISoftDeletable.IsDeleted)).IsModified = true; - entityEntry.Member(nameof(ISoftDeletable.DeletedById)).IsModified = true; - entityEntry.Member(nameof(ISoftDeletable.DeletedOn)).IsModified = true; + ChangeTracker.CascadeDeleteTiming = originalCascadeDeleteTiming; + ChangeTracker.DeleteOrphansTiming = originalDeleteOrphansTiming; } + } - foreach (var entityEntry in ChangeTracker.Entries>().Where(c => c.State == EntityState.Added)) + public void Reset() + { + var originalCascadeDeleteTiming = ChangeTracker.CascadeDeleteTiming; + var originalDeleteOrphansTiming = ChangeTracker.DeleteOrphansTiming; + try { - entityEntry.Entity.CreatedById = authenticatedUserIdentifier; - entityEntry.Entity.CreatedOn = DateTime.UtcNow; - } + ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; + ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; - foreach (var entityEntry in ChangeTracker.Entries>().Where(c => c.State == EntityState.Modified)) + var entries = ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged).ToArray(); + foreach (var entry in entries) + { + switch (entry.State) + { + case EntityState.Modified: + entry.State = EntityState.Unchanged; + break; + case EntityState.Added: + entry.State = EntityState.Detached; + break; + case EntityState.Deleted: + entry.Reload(); + break; + } + } + } + finally { - entityEntry.Entity.ModifiedById = authenticatedUserIdentifier; - entityEntry.Entity.ModifiedOn = DateTime.UtcNow; + ChangeTracker.CascadeDeleteTiming = originalCascadeDeleteTiming; + ChangeTracker.DeleteOrphansTiming = originalDeleteOrphansTiming; } } } diff --git a/DataAccessClient.EntityFrameworkCore.SqlServer/UnitOfWorkPartForSqlServerDbContext.cs b/DataAccessClient.EntityFrameworkCore.SqlServer/UnitOfWorkPartForSqlServerDbContext.cs index ba04827..e9c9bd0 100644 --- a/DataAccessClient.EntityFrameworkCore.SqlServer/UnitOfWorkPartForSqlServerDbContext.cs +++ b/DataAccessClient.EntityFrameworkCore.SqlServer/UnitOfWorkPartForSqlServerDbContext.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; namespace DataAccessClient.EntityFrameworkCore.SqlServer { @@ -23,22 +21,7 @@ public async Task SaveAsync() public void Reset() { - var entries = _dbContext.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged).ToArray(); - foreach (var entry in entries) - { - switch (entry.State) - { - case EntityState.Modified: - entry.State = EntityState.Unchanged; - break; - case EntityState.Added: - entry.State = EntityState.Detached; - break; - case EntityState.Deleted: - entry.Reload(); - break; - } - } + _dbContext.Reset(); } } } \ No newline at end of file