@@ -51,11 +51,14 @@ public interface ITestIsolationOptions
51
51
/// Deletes all records from the tables using "DELETE".
52
52
/// No ambient transaction; no unique schema.
53
53
/// </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 )
55
57
{
56
58
return new DeleteAllData ( filter is null
57
59
? SkipTempTablesAndCollectionParameters
58
- : entityType => filter ( entityType ) && SkipTempTablesAndCollectionParameters ( entityType ) ) ;
60
+ : entityType => filter ( entityType ) && SkipTempTablesAndCollectionParameters ( entityType ) ,
61
+ modifyDeletionOrder ) ;
59
62
}
60
63
61
64
private static bool SkipTempTablesAndCollectionParameters ( IEntityType entityType )
@@ -188,52 +191,65 @@ public async ValueTask CleanupAsync(DbContext dbContext, string? schema, Cancell
188
191
189
192
private class DeleteAllData : ITestIsolationOptions
190
193
{
191
- private readonly Predicate < IEntityType > _filter ;
192
-
193
194
private static readonly MethodInfo _deleteData = typeof ( DeleteAllData ) . GetMethod ( nameof ( DeleteDataAsync ) , BindingFlags . Static | BindingFlags . NonPublic )
194
195
?? throw new Exception ( $ "Method '{ nameof ( DeleteDataAsync ) } ' not found.") ;
195
196
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 ;
197
202
198
203
public bool NeedsAmbientTransaction => false ;
199
204
public bool NeedsUniqueSchema => false ;
200
205
public bool NeedsCleanup => true ;
201
206
202
- public DeleteAllData ( Predicate < IEntityType > filter )
207
+ public DeleteAllData (
208
+ Predicate < IEntityType > filter ,
209
+ Func < IReadOnlyList < IEntityType > , IReadOnlyList < IEntityType > > ? modifyDeletionOrder )
203
210
{
204
211
_filter = filter ;
205
- _deleteDelegatesLookup = new ConcurrentDictionary < Type , Func < DbContext , CancellationToken , Task > > ( ) ;
212
+ _modifyDeletionOrder = modifyDeletionOrder ;
213
+ _deleteDelegatesLookup = new ConcurrentDictionary < IEntityType , Func < DbContext , string , CancellationToken , Task > > ( ) ;
206
214
}
207
215
208
216
[ SuppressMessage ( "Usage" , "EF1001:Internal EF Core API usage." ) ]
209
217
public async ValueTask CleanupAsync ( DbContext dbContext , string ? schema , CancellationToken cancellationToken )
210
218
{
211
- foreach ( var entityType in dbContext . Model . GetEntityTypesInHierarchicalOrder ( ) . Reverse ( ) )
219
+ if ( _orderedEntities is null )
212
220
{
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 ) ;
216
232
217
- await delete ( dbContext , cancellationToken ) ;
218
- }
233
+ await delete ( dbContext , entityType . Name , cancellationToken ) ;
219
234
}
220
235
}
221
236
222
- private Func < DbContext , CancellationToken , Task > CreateDelegate ( Type type )
237
+ private static Func < DbContext , string , CancellationToken , Task > CreateDelegate ( IEntityType type )
223
238
{
224
239
var ctxParam = Expression . Parameter ( typeof ( DbContext ) ) ;
240
+ var nameParam = Expression . Parameter ( typeof ( string ) ) ;
225
241
var cancellationTokenParam = Expression . Parameter ( typeof ( CancellationToken ) ) ;
226
- var method = _deleteData . MakeGenericMethod ( type ) ;
242
+ var method = _deleteData . MakeGenericMethod ( type . ClrType ) ;
227
243
228
- var call = Expression . Call ( method , ctxParam , cancellationTokenParam ) ;
244
+ var call = Expression . Call ( method , ctxParam , nameParam , cancellationTokenParam ) ;
229
245
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 ( ) ;
231
247
}
232
248
233
- private static async Task DeleteDataAsync < T > ( DbContext dbContext , CancellationToken cancellationToken )
249
+ private static async Task DeleteDataAsync < T > ( DbContext dbContext , string name , CancellationToken cancellationToken )
234
250
where T : class
235
251
{
236
- await dbContext . Set < T > ( ) . ExecuteDeleteAsync ( cancellationToken ) ;
252
+ await dbContext . Set < T > ( name ) . ExecuteDeleteAsync ( cancellationToken ) ;
237
253
}
238
254
}
239
255
0 commit comments