Skip to content

Commit

Permalink
Due to change in changetracking behavior for ChangeTracker.CascadeDel…
Browse files Browse the repository at this point in the history
…eteTiming 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.
  • Loading branch information
Henk Kin committed Feb 7, 2020
1 parent de95d55 commit 9d469a0
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageReleaseNotes></PackageReleaseNotes>
<Copyright>Henk Kin</Copyright>
<PackageTags>EntityFrameworkCore, EF, SqlServer, DependencyInjection, Multiple DbContext support, DataAccess, Repository, UnitOfWork, SoftDelete, Translation, RowVersioning, Filtering, Paging, Sorting, Includes</PackageTags>
<Version>0.0.2</Version>
<Version>0.0.3</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
170 changes: 123 additions & 47 deletions DataAccessClient.EntityFrameworkCore.SqlServer/SqlServerDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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[]
{
Expand All @@ -43,47 +54,62 @@ 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)))
{
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);
Expand Down Expand Up @@ -128,11 +154,13 @@ public override int SaveChanges(bool acceptAllChangesOnSuccess)
}

}

throw;
}
}

public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken())
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = new CancellationToken())
{
var authenticatedUser = await _authenticatedUserIdentifierProvider.ExecuteAsync();

Expand Down Expand Up @@ -174,36 +202,84 @@ public override int SaveChanges(bool acceptAllChangesOnSuccess)

private void AdjustEntities(TIdentifierType authenticatedUserIdentifier)
{
foreach (var entityEntry in ChangeTracker.Entries<IRowVersionable>())
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<IRowVersionable>())
{
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<ISoftDeletable<TIdentifierType>>()
.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<TIdentifierType>.IsDeleted)).IsModified = true;
entityEntry.Member(nameof(ISoftDeletable<TIdentifierType>.DeletedById)).IsModified = true;
entityEntry.Member(nameof(ISoftDeletable<TIdentifierType>.DeletedOn)).IsModified = true;
}

foreach (var entityEntry in ChangeTracker.Entries<ICreatable<TIdentifierType>>()
.Where(c => c.State == EntityState.Added))
{
entityEntry.Entity.CreatedById = authenticatedUserIdentifier;
entityEntry.Entity.CreatedOn = DateTime.UtcNow;
}

foreach (var entityEntry in ChangeTracker.Entries<ISoftDeletable<TIdentifierType>>().Where(c => c.State == EntityState.Deleted))
foreach (var entityEntry in ChangeTracker.Entries<IModifiable<TIdentifierType>>()
.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<TIdentifierType>.IsDeleted)).IsModified = true;
entityEntry.Member(nameof(ISoftDeletable<TIdentifierType>.DeletedById)).IsModified = true;
entityEntry.Member(nameof(ISoftDeletable<TIdentifierType>.DeletedOn)).IsModified = true;
ChangeTracker.CascadeDeleteTiming = originalCascadeDeleteTiming;
ChangeTracker.DeleteOrphansTiming = originalDeleteOrphansTiming;
}
}

foreach (var entityEntry in ChangeTracker.Entries<ICreatable<TIdentifierType>>().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<IModifiable<TIdentifierType>>().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;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace DataAccessClient.EntityFrameworkCore.SqlServer
{
Expand All @@ -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();
}
}
}

0 comments on commit 9d469a0

Please sign in to comment.