diff --git a/Directory.Build.props b/Directory.Build.props index d0da74f3..73030b0c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ (c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved. - 7.2.1 + 7.2.2 Pawel Gerr true https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs index a3ae6b00..a9e89ed9 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs @@ -51,11 +51,14 @@ public interface ITestIsolationOptions /// Deletes all records from the tables using "DELETE". /// No ambient transaction; no unique schema. /// - public static ITestIsolationOptions DeleteData(Predicate? filter = null) + public static ITestIsolationOptions DeleteData( + Predicate? filter = null, + Func, IReadOnlyList>? modifyDeletionOrder = null) { return new DeleteAllData(filter is null ? SkipTempTablesAndCollectionParameters - : entityType => filter(entityType) && SkipTempTablesAndCollectionParameters(entityType)); + : entityType => filter(entityType) && SkipTempTablesAndCollectionParameters(entityType), + modifyDeletionOrder); } private static bool SkipTempTablesAndCollectionParameters(IEntityType entityType) @@ -188,52 +191,65 @@ public async ValueTask CleanupAsync(DbContext dbContext, string? schema, Cancell private class DeleteAllData : ITestIsolationOptions { - private readonly Predicate _filter; - private static readonly MethodInfo _deleteData = typeof(DeleteAllData).GetMethod(nameof(DeleteDataAsync), BindingFlags.Static | BindingFlags.NonPublic) ?? throw new Exception($"Method '{nameof(DeleteDataAsync)}' not found."); - private readonly ConcurrentDictionary> _deleteDelegatesLookup; + private readonly Predicate _filter; + private readonly Func, IReadOnlyList>? _modifyDeletionOrder; + private readonly ConcurrentDictionary> _deleteDelegatesLookup; + + private IReadOnlyList? _orderedEntities; public bool NeedsAmbientTransaction => false; public bool NeedsUniqueSchema => false; public bool NeedsCleanup => true; - public DeleteAllData(Predicate filter) + public DeleteAllData( + Predicate filter, + Func, IReadOnlyList>? modifyDeletionOrder) { _filter = filter; - _deleteDelegatesLookup = new ConcurrentDictionary>(); + _modifyDeletionOrder = modifyDeletionOrder; + _deleteDelegatesLookup = new ConcurrentDictionary>(); } [SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) { - foreach (var entityType in dbContext.Model.GetEntityTypesInHierarchicalOrder().Reverse()) + if (_orderedEntities is null) { - if (entityType.GetTableName() is not null && _filter(entityType)) - { - var delete = _deleteDelegatesLookup.GetOrAdd(entityType.ClrType, CreateDelegate); + var orderedEntities = dbContext.Model.GetEntityTypesInHierarchicalOrder().Reverse().ToList(); + + _orderedEntities = _modifyDeletionOrder?.Invoke(orderedEntities) ?? orderedEntities; + } + + foreach (var entityType in _orderedEntities) + { + if (entityType.GetTableName() is null || !_filter(entityType)) + continue; + + var delete = _deleteDelegatesLookup.GetOrAdd(entityType, CreateDelegate); - await delete(dbContext, cancellationToken); - } + await delete(dbContext, entityType.Name, cancellationToken); } } - private Func CreateDelegate(Type type) + private static Func CreateDelegate(IEntityType type) { var ctxParam = Expression.Parameter(typeof(DbContext)); + var nameParam = Expression.Parameter(typeof(string)); var cancellationTokenParam = Expression.Parameter(typeof(CancellationToken)); - var method = _deleteData.MakeGenericMethod(type); + var method = _deleteData.MakeGenericMethod(type.ClrType); - var call = Expression.Call(method, ctxParam, cancellationTokenParam); + var call = Expression.Call(method, ctxParam, nameParam, cancellationTokenParam); - return Expression.Lambda>(call, ctxParam, cancellationTokenParam).Compile(); + return Expression.Lambda>(call, ctxParam, nameParam, cancellationTokenParam).Compile(); } - private static async Task DeleteDataAsync(DbContext dbContext, CancellationToken cancellationToken) + private static async Task DeleteDataAsync(DbContext dbContext, string name, CancellationToken cancellationToken) where T : class { - await dbContext.Set().ExecuteDeleteAsync(cancellationToken); + await dbContext.Set(name).ExecuteDeleteAsync(cancellationToken); } }