@@ -51,11 +51,14 @@ public interface ITestIsolationOptions
5151 /// Deletes all records from the tables using "DELETE".
5252 /// No ambient transaction; no unique schema.
5353 /// </summary>
54- public static ITestIsolationOptions DeleteData ( Predicate < IEntityType > ? filter = null )
54+ public static ITestIsolationOptions DeleteData (
55+ Predicate < IEntityType > ? filter = null ,
56+ Func < IReadOnlyList < IEntityType > , IReadOnlyList < IEntityType > > ? modifyDeletionOrder = null )
5557 {
5658 return new DeleteAllData ( filter is null
5759 ? SkipTempTablesAndCollectionParameters
58- : entityType => filter ( entityType ) && SkipTempTablesAndCollectionParameters ( entityType ) ) ;
60+ : entityType => filter ( entityType ) && SkipTempTablesAndCollectionParameters ( entityType ) ,
61+ modifyDeletionOrder ) ;
5962 }
6063
6164 private static bool SkipTempTablesAndCollectionParameters ( IEntityType entityType )
@@ -188,52 +191,65 @@ public async ValueTask CleanupAsync(DbContext dbContext, string? schema, Cancell
188191
189192 private class DeleteAllData : ITestIsolationOptions
190193 {
191- private readonly Predicate < IEntityType > _filter ;
192-
193194 private static readonly MethodInfo _deleteData = typeof ( DeleteAllData ) . GetMethod ( nameof ( DeleteDataAsync ) , BindingFlags . Static | BindingFlags . NonPublic )
194195 ?? throw new Exception ( $ "Method '{ nameof ( DeleteDataAsync ) } ' not found.") ;
195196
196- private readonly ConcurrentDictionary < Type , Func < DbContext , CancellationToken , Task > > _deleteDelegatesLookup ;
197+ private readonly Predicate < IEntityType > _filter ;
198+ private readonly Func < IReadOnlyList < IEntityType > , IReadOnlyList < IEntityType > > ? _modifyDeletionOrder ;
199+ private readonly ConcurrentDictionary < IEntityType , Func < DbContext , string , CancellationToken , Task > > _deleteDelegatesLookup ;
200+
201+ private IReadOnlyList < IEntityType > ? _orderedEntities ;
197202
198203 public bool NeedsAmbientTransaction => false ;
199204 public bool NeedsUniqueSchema => false ;
200205 public bool NeedsCleanup => true ;
201206
202- public DeleteAllData ( Predicate < IEntityType > filter )
207+ public DeleteAllData (
208+ Predicate < IEntityType > filter ,
209+ Func < IReadOnlyList < IEntityType > , IReadOnlyList < IEntityType > > ? modifyDeletionOrder )
203210 {
204211 _filter = filter ;
205- _deleteDelegatesLookup = new ConcurrentDictionary < Type , Func < DbContext , CancellationToken , Task > > ( ) ;
212+ _modifyDeletionOrder = modifyDeletionOrder ;
213+ _deleteDelegatesLookup = new ConcurrentDictionary < IEntityType , Func < DbContext , string , CancellationToken , Task > > ( ) ;
206214 }
207215
208216 [ SuppressMessage ( "Usage" , "EF1001:Internal EF Core API usage." ) ]
209217 public async ValueTask CleanupAsync ( DbContext dbContext , string ? schema , CancellationToken cancellationToken )
210218 {
211- foreach ( var entityType in dbContext . Model . GetEntityTypesInHierarchicalOrder ( ) . Reverse ( ) )
219+ if ( _orderedEntities is null )
212220 {
213- if ( entityType . GetTableName ( ) is not null && _filter ( entityType ) )
214- {
215- var delete = _deleteDelegatesLookup . GetOrAdd ( entityType . ClrType , CreateDelegate ) ;
221+ var orderedEntities = dbContext . Model . GetEntityTypesInHierarchicalOrder ( ) . Reverse ( ) . ToList ( ) ;
222+
223+ _orderedEntities = _modifyDeletionOrder ? . Invoke ( orderedEntities ) ?? orderedEntities ;
224+ }
225+
226+ foreach ( var entityType in _orderedEntities )
227+ {
228+ if ( entityType . GetTableName ( ) is null || ! _filter ( entityType ) )
229+ continue ;
230+
231+ var delete = _deleteDelegatesLookup . GetOrAdd ( entityType , CreateDelegate ) ;
216232
217- await delete ( dbContext , cancellationToken ) ;
218- }
233+ await delete ( dbContext , entityType . Name , cancellationToken ) ;
219234 }
220235 }
221236
222- private Func < DbContext , CancellationToken , Task > CreateDelegate ( Type type )
237+ private static Func < DbContext , string , CancellationToken , Task > CreateDelegate ( IEntityType type )
223238 {
224239 var ctxParam = Expression . Parameter ( typeof ( DbContext ) ) ;
240+ var nameParam = Expression . Parameter ( typeof ( string ) ) ;
225241 var cancellationTokenParam = Expression . Parameter ( typeof ( CancellationToken ) ) ;
226- var method = _deleteData . MakeGenericMethod ( type ) ;
242+ var method = _deleteData . MakeGenericMethod ( type . ClrType ) ;
227243
228- var call = Expression . Call ( method , ctxParam , cancellationTokenParam ) ;
244+ var call = Expression . Call ( method , ctxParam , nameParam , cancellationTokenParam ) ;
229245
230- return Expression . Lambda < Func < DbContext , CancellationToken , Task > > ( call , ctxParam , cancellationTokenParam ) . Compile ( ) ;
246+ return Expression . Lambda < Func < DbContext , string , CancellationToken , Task > > ( call , ctxParam , nameParam , cancellationTokenParam ) . Compile ( ) ;
231247 }
232248
233- private static async Task DeleteDataAsync < T > ( DbContext dbContext , CancellationToken cancellationToken )
249+ private static async Task DeleteDataAsync < T > ( DbContext dbContext , string name , CancellationToken cancellationToken )
234250 where T : class
235251 {
236- await dbContext . Set < T > ( ) . ExecuteDeleteAsync ( cancellationToken ) ;
252+ await dbContext . Set < T > ( name ) . ExecuteDeleteAsync ( cancellationToken ) ;
237253 }
238254 }
239255
0 commit comments