Skip to content

Commit baea642

Browse files
committed
Added additional ITestIsolationOptions which uses DELETE FROM instead of TRUNCATE
1 parent c21ebba commit baea642

File tree

1 file changed

+78
-2
lines changed

1 file changed

+78
-2
lines changed

src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
using System.Collections.Concurrent;
12
using System.Diagnostics.CodeAnalysis;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
25
using Microsoft.Data.SqlClient;
36
using Microsoft.EntityFrameworkCore.Infrastructure;
7+
using Microsoft.EntityFrameworkCore.Metadata;
48
using Microsoft.EntityFrameworkCore.Metadata.Internal;
59
using Microsoft.EntityFrameworkCore.Migrations;
610
using Microsoft.EntityFrameworkCore.Storage;
11+
using Thinktecture.EntityFrameworkCore.Parameters;
12+
using Thinktecture.EntityFrameworkCore.TempTables;
713

814
namespace Thinktecture.EntityFrameworkCore.Testing;
915

@@ -36,11 +42,30 @@ public interface ITestIsolationOptions
3642
public static readonly ITestIsolationOptions CleanupOnly = new CleanupDatabase();
3743

3844
/// <summary>
39-
/// Deletes all records from the tables.
45+
/// Deletes all records from the tables using "TRUNCATE".
4046
/// No ambient transaction; no unique schema.
4147
/// </summary>
4248
public static readonly ITestIsolationOptions TruncateTables = new TruncateAllTables();
4349

50+
/// <summary>
51+
/// Deletes all records from the tables using "DELETE".
52+
/// No ambient transaction; no unique schema.
53+
/// </summary>
54+
public static ITestIsolationOptions DeleteData(Predicate<IEntityType>? filter = null)
55+
{
56+
return new DeleteAllData(filter is null
57+
? SkipTempTablesAndCollectionParameters
58+
: entityType => filter(entityType) && SkipTempTablesAndCollectionParameters(entityType));
59+
}
60+
61+
private static bool SkipTempTablesAndCollectionParameters(IEntityType entityType)
62+
{
63+
return !entityType.ClrType.IsGenericType
64+
|| (entityType.ClrType.GetGenericTypeDefinition() != typeof(TempTable<>)
65+
&& entityType.ClrType.GetGenericTypeDefinition() != typeof(TempTable<,>)
66+
&& entityType.ClrType.GetGenericTypeDefinition() != typeof(ScalarCollectionParameter<>));
67+
}
68+
4469
/// <summary>
4570
/// Performs custom cleanup.
4671
/// </summary>
@@ -144,13 +169,13 @@ public async ValueTask CleanupAsync(DbContext dbContext, string? schema, Cancell
144169
}
145170
}
146171

147-
[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")]
148172
private class TruncateAllTables : ITestIsolationOptions
149173
{
150174
public bool NeedsAmbientTransaction => false;
151175
public bool NeedsUniqueSchema => false;
152176
public bool NeedsCleanup => true;
153177

178+
[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")]
154179
public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken)
155180
{
156181
foreach (var entityType in dbContext.Model.GetEntityTypesInHierarchicalOrder().Reverse())
@@ -161,6 +186,57 @@ public async ValueTask CleanupAsync(DbContext dbContext, string? schema, Cancell
161186
}
162187
}
163188

189+
private class DeleteAllData : ITestIsolationOptions
190+
{
191+
private readonly Predicate<IEntityType> _filter;
192+
193+
private static readonly MethodInfo _deleteData = typeof(DeleteAllData).GetMethod(nameof(DeleteDataAsync), BindingFlags.Static | BindingFlags.NonPublic)
194+
?? throw new Exception($"Method '{nameof(DeleteDataAsync)}' not found.");
195+
196+
private readonly ConcurrentDictionary<Type, Func<DbContext, CancellationToken, Task>> _deleteDelegatesLookup;
197+
198+
public bool NeedsAmbientTransaction => false;
199+
public bool NeedsUniqueSchema => false;
200+
public bool NeedsCleanup => true;
201+
202+
public DeleteAllData(Predicate<IEntityType> filter)
203+
{
204+
_filter = filter;
205+
_deleteDelegatesLookup = new ConcurrentDictionary<Type, Func<DbContext, CancellationToken, Task>>();
206+
}
207+
208+
[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")]
209+
public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken)
210+
{
211+
foreach (var entityType in dbContext.Model.GetEntityTypesInHierarchicalOrder().Reverse())
212+
{
213+
if (entityType.GetTableName() is not null && _filter(entityType))
214+
{
215+
var delete = _deleteDelegatesLookup.GetOrAdd(entityType.ClrType, CreateDelegate);
216+
217+
await delete(dbContext, cancellationToken);
218+
}
219+
}
220+
}
221+
222+
private Func<DbContext, CancellationToken, Task> CreateDelegate(Type type)
223+
{
224+
var ctxParam = Expression.Parameter(typeof(DbContext));
225+
var cancellationTokenParam = Expression.Parameter(typeof(CancellationToken));
226+
var method = _deleteData.MakeGenericMethod(type);
227+
228+
var call = Expression.Call(method, ctxParam, cancellationTokenParam);
229+
230+
return Expression.Lambda<Func<DbContext, CancellationToken, Task>>(call, ctxParam, cancellationTokenParam).Compile();
231+
}
232+
233+
private static async Task DeleteDataAsync<T>(DbContext dbContext, CancellationToken cancellationToken)
234+
where T : class
235+
{
236+
await dbContext.Set<T>().ExecuteDeleteAsync(cancellationToken);
237+
}
238+
}
239+
164240
/// <summary>
165241
/// Rollbacks all migrations.
166242
/// </summary>

0 commit comments

Comments
 (0)