Skip to content

Commit 3ad81ab

Browse files
committed
Implemented SqliteTestDbContextProviderFactory that creates new providers with operate on a clone of previously migrated database.
1 parent 5934f94 commit 3ad81ab

File tree

10 files changed

+696
-499
lines changed

10 files changed

+696
-499
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ protected internal SqlServerTestDbContextProvider(SqlServerTestDbContextProvider
9898
_sharedTablesIsolationLevel = ValidateIsolationLevel(options.SharedTablesIsolationLevel);
9999
_isolationOptions = options.IsolationOptions;
100100
_masterConnection = options.MasterConnection ?? throw new ArgumentException($"The '{nameof(options.MasterConnection)}' cannot be null.", nameof(options));
101-
_masterDbContextOptions = options.MasterDbContextOptions ?? throw new ArgumentException($"The '{nameof(options.MasterDbContextOptions)}' cannot be null.", nameof(options));
102-
_dbContextOptions = options.DbContextOptions ?? throw new ArgumentException($"The '{nameof(options.DbContextOptions)}' cannot be null.", nameof(options));
101+
_masterDbContextOptions = options.MasterDbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.MasterDbContextOptionsBuilder)}' cannot be null.", nameof(options));
102+
_dbContextOptions = options.DbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.DbContextOptionsBuilder)}' cannot be null.", nameof(options));
103103
_migrationExecutionStrategy = options.MigrationExecutionStrategy ?? throw new ArgumentException($"The '{nameof(options.MigrationExecutionStrategy)}' cannot be null.", nameof(options));
104104
_testingLoggingOptions = options.TestingLoggingOptions ?? throw new ArgumentException($"The '{nameof(options.TestingLoggingOptions)}' cannot be null.", nameof(options));
105105
_contextInitializations = options.ContextInitializations ?? throw new ArgumentException($"The '{nameof(options.ContextInitializations)}' cannot be null.", nameof(options));

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,13 @@ public SqlServerTestDbContextProvider<T> Build()
391391
{
392392
var loggingOptions = CreateLoggingOptions();
393393
var state = new TestDbContextProviderBuilderState(loggingOptions);
394-
var masterDbContextOptions = CreateOptionsBuilder(state, masterConnection, schema).Options;
395-
var dbContextOptions = CreateOptionsBuilder(state, null, schema).Options;
394+
var masterDbContextOptionsBuilder = CreateOptionsBuilder(state, masterConnection, schema);
395+
var dbContextOptionsBuilder = CreateOptionsBuilder(state, null, schema);
396396

397397
var options = new SqlServerTestDbContextProviderOptions<T>(masterConnection,
398398
state.MigrationExecutionStrategy ?? IMigrationExecutionStrategy.Migrations,
399-
masterDbContextOptions,
400-
dbContextOptions,
399+
masterDbContextOptionsBuilder,
400+
dbContextOptionsBuilder,
401401
loggingOptions,
402402
_ctxInitializations.ToList(),
403403
schema)

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

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Data;
2-
using System.Data.Common;
32
using System.Diagnostics.CodeAnalysis;
3+
using Microsoft.Data.SqlClient;
44
using Thinktecture.Logging;
55

66
namespace Thinktecture.EntityFrameworkCore.Testing;
@@ -13,25 +13,19 @@ public class SqlServerTestDbContextProviderOptions<T> : TestDbContextProviderOpt
1313
where T : DbContext
1414
{
1515
/// <summary>
16-
/// Indication whether the current <see cref="SqlServerTestDbContextProvider{T}"/> is using its own tables with a new schema
17-
/// or shares the tables with others.
16+
/// Master database connection.
1817
/// </summary>
19-
[Obsolete($"Use '{nameof(IsolationOptions)}' instead.")]
20-
public bool IsUsingSharedTables
21-
{
22-
get => IsolationOptions == ITestIsolationOptions.SharedTablesAmbientTransaction;
23-
set => IsolationOptions = value ? ITestIsolationOptions.SharedTablesAmbientTransaction : ITestIsolationOptions.RollbackMigrationsAndCleanup;
24-
}
18+
public new SqlConnection MasterConnection => (SqlConnection)base.MasterConnection;
2519

26-
private ITestIsolationOptions? _isolationOptions;
20+
private readonly ITestIsolationOptions? _isolationOptions;
2721

2822
/// <summary>
2923
/// Test isolation behavior.
3024
/// </summary>
3125
public ITestIsolationOptions IsolationOptions
3226
{
3327
get => _isolationOptions ?? ITestIsolationOptions.SharedTablesAmbientTransaction;
34-
set => _isolationOptions = value;
28+
init => _isolationOptions = value;
3529
}
3630

3731
/// <summary>
@@ -42,13 +36,13 @@ public ITestIsolationOptions IsolationOptions
4236
/// <summary>
4337
/// A factory method for creation of contexts of type <typeparamref name="T"/>.
4438
/// </summary>
45-
public Func<DbContextOptions<T>, IDbDefaultSchema?, T?>? ContextFactory { get; set; }
39+
public Func<DbContextOptions<T>, IDbDefaultSchema?, T?>? ContextFactory { get; init; }
4640

4741
/// <summary>
4842
/// Isolation level to be used with shared tables.
4943
/// Default is <see cref="IsolationLevel.ReadCommitted"/>.
5044
/// </summary>
51-
public IsolationLevel? SharedTablesIsolationLevel { get; set; }
45+
public IsolationLevel? SharedTablesIsolationLevel { get; init; }
5246

5347
private SqlServerLockTableOptions? _lockTable;
5448

@@ -59,21 +53,21 @@ public ITestIsolationOptions IsolationOptions
5953
public SqlServerLockTableOptions LockTable
6054
{
6155
get => _lockTable ??= new SqlServerLockTableOptions(true);
62-
set => _lockTable = value;
56+
init => _lockTable = value;
6357
}
6458

6559
/// <summary>
6660
/// Initializes new instance of <see cref="SqlServerTestDbContextProviderOptions{T}"/>.
6761
/// </summary>
6862
public SqlServerTestDbContextProviderOptions(
69-
DbConnection masterConnection,
63+
SqlConnection masterConnection,
7064
IMigrationExecutionStrategy migrationExecutionStrategy,
71-
DbContextOptions<T> masterDbContextOptions,
72-
DbContextOptions<T> dbContextOptions,
65+
DbContextOptionsBuilder<T> masterDbContextOptionsBuilder,
66+
DbContextOptionsBuilder<T> dbContextOptionsBuilder,
7367
TestingLoggingOptions testingLoggingOptions,
7468
IReadOnlyList<Action<T>> contextInitializations,
7569
string? schema)
76-
: base(masterConnection, migrationExecutionStrategy, masterDbContextOptions, dbContextOptions, testingLoggingOptions, contextInitializations)
70+
: base(masterConnection, migrationExecutionStrategy, masterDbContextOptionsBuilder, dbContextOptionsBuilder, testingLoggingOptions, contextInitializations)
7771
{
7872
Schema = schema;
7973
}
Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,43 @@
1-
using System.Data.Common;
2-
using Thinktecture.Logging;
3-
4-
namespace Thinktecture.EntityFrameworkCore.Testing;
5-
6-
/// <summary>
7-
/// Options for the <see cref="SqliteTestDbContextProvider{T}"/>.
8-
/// </summary>
9-
/// <typeparam name="T"></typeparam>
10-
public class SqliteTestDbContextProviderOptions<T> : TestDbContextProviderOptions<T>
11-
where T : DbContext
12-
{
13-
/// <summary>
14-
/// A factory method for creation of contexts of type <typeparamref name="T"/>.
15-
/// </summary>
16-
public Func<DbContextOptions<T>, T?>? ContextFactory { get; set; }
17-
18-
/// <summary>
19-
/// The connection string.
20-
/// </summary>
21-
public string ConnectionString { get; set; }
22-
23-
/// <summary>
24-
/// Initializes new instance of <see cref="SqliteTestDbContextProviderOptions{T}"/>
25-
/// </summary>
26-
public SqliteTestDbContextProviderOptions(
27-
DbConnection masterConnection,
28-
IMigrationExecutionStrategy migrationExecutionStrategy,
29-
DbContextOptions<T> masterDbContextOptions,
30-
DbContextOptions<T> dbContextOptions,
31-
TestingLoggingOptions testingLoggingOptions,
32-
IReadOnlyList<Action<T>> contextInitializations,
33-
string connectionString)
34-
: base(masterConnection, migrationExecutionStrategy, masterDbContextOptions, dbContextOptions, testingLoggingOptions, contextInitializations)
35-
{
36-
ConnectionString = connectionString;
37-
}
38-
}
1+
using Microsoft.Data.Sqlite;
2+
using Thinktecture.Logging;
3+
4+
namespace Thinktecture.EntityFrameworkCore.Testing;
5+
6+
/// <summary>
7+
/// Options for the <see cref="SqliteTestDbContextProvider{T}"/>.
8+
/// </summary>
9+
/// <typeparam name="T"></typeparam>
10+
public class SqliteTestDbContextProviderOptions<T> : TestDbContextProviderOptions<T>
11+
where T : DbContext
12+
{
13+
/// <summary>
14+
/// Master database connection.
15+
/// </summary>
16+
public new SqliteConnection MasterConnection => (SqliteConnection)base.MasterConnection;
17+
18+
/// <summary>
19+
/// Indication whether the master connection is externally owned.
20+
/// </summary>
21+
public bool IsExternallyOwnedMasterConnection { get; }
22+
23+
/// <summary>
24+
/// A factory method for creation of contexts of type <typeparamref name="T"/>.
25+
/// </summary>
26+
public Func<DbContextOptions<T>, T?>? ContextFactory { get; init; }
27+
28+
/// <summary>
29+
/// Initializes new instance of <see cref="SqliteTestDbContextProviderOptions{T}"/>
30+
/// </summary>
31+
public SqliteTestDbContextProviderOptions(
32+
SqliteConnection masterConnection,
33+
bool isExternallyOwnedMasterConnection,
34+
IMigrationExecutionStrategy migrationExecutionStrategy,
35+
DbContextOptionsBuilder<T> masterDbContextOptionsBuilder,
36+
DbContextOptionsBuilder<T> dbContextOptionsBuilder,
37+
TestingLoggingOptions testingLoggingOptions,
38+
IReadOnlyList<Action<T>> contextInitializations)
39+
: base(masterConnection, migrationExecutionStrategy, masterDbContextOptionsBuilder, dbContextOptionsBuilder, testingLoggingOptions, contextInitializations)
40+
{
41+
IsExternallyOwnedMasterConnection = isExternallyOwnedMasterConnection;
42+
}
43+
}

src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProvider.cs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Data.Common;
1+
using System.Data;
2+
using Microsoft.Data.Sqlite;
23
using Microsoft.EntityFrameworkCore.Infrastructure;
34
using Thinktecture.Logging;
45

@@ -11,13 +12,12 @@ namespace Thinktecture.EntityFrameworkCore.Testing;
1112
public class SqliteTestDbContextProvider<T> : ITestDbContextProvider<T>
1213
where T : DbContext
1314
{
14-
// ReSharper disable once StaticMemberInGenericType because the lock are all for the same database context.
15-
private static readonly object _lock = new();
15+
private readonly object _lock = new();
1616

1717
private readonly DbContextOptions<T> _masterDbContextOptions;
1818
private readonly DbContextOptions<T> _dbContextOptions;
1919
private readonly IMigrationExecutionStrategy _migrationExecutionStrategy;
20-
private readonly DbConnection _masterConnection;
20+
private readonly bool _isExternallyOwnedMasterConnection;
2121
private readonly IReadOnlyList<Action<T>> _contextInitializations;
2222
private readonly Func<DbContextOptions<T>, T?>? _contextFactory;
2323

@@ -28,6 +28,11 @@ public class SqliteTestDbContextProvider<T> : ITestDbContextProvider<T>
2828
private readonly TestingLoggingOptions _testingLoggingOptions;
2929
private bool _isDisposed;
3030

31+
/// <summary>
32+
/// A database connection which kept open during the test, so the in-memory database is not deleted.
33+
/// </summary>
34+
public SqliteConnection MasterConnection { get; }
35+
3136
/// <inheritdoc />
3237
public T ArrangeDbContext => _arrangeDbContext ??= CreateDbContext(true);
3338

@@ -50,7 +55,7 @@ public class SqliteTestDbContextProvider<T> : ITestDbContextProvider<T>
5055
/// <summary>
5156
/// The connection string.
5257
/// </summary>
53-
public string ConnectionString { get; }
58+
public string ConnectionString => MasterConnection.ConnectionString;
5459

5560
/// <summary>
5661
/// Initializes a new instance of <see cref="SqliteTestDbContextProvider{T}"/>
@@ -60,10 +65,10 @@ protected internal SqliteTestDbContextProvider(SqliteTestDbContextProviderOption
6065
{
6166
ArgumentNullException.ThrowIfNull(options);
6267

63-
ConnectionString = options.ConnectionString ?? throw new ArgumentException($"The '{nameof(options.ConnectionString)}' cannot be null.", nameof(options));
64-
_masterConnection = options.MasterConnection ?? throw new ArgumentException($"The '{nameof(options.MasterConnection)}' cannot be null.", nameof(options));
65-
_masterDbContextOptions = options.MasterDbContextOptions ?? throw new ArgumentException($"The '{nameof(options.MasterDbContextOptions)}' cannot be null.", nameof(options));
66-
_dbContextOptions = options.DbContextOptions ?? throw new ArgumentException($"The '{nameof(options.DbContextOptions)}' cannot be null.", nameof(options));
68+
MasterConnection = options.MasterConnection ?? throw new ArgumentException($"The '{nameof(options.MasterConnection)}' cannot be null.", nameof(options));
69+
_isExternallyOwnedMasterConnection = options.IsExternallyOwnedMasterConnection;
70+
_masterDbContextOptions = options.MasterDbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.MasterDbContextOptionsBuilder)}' cannot be null.", nameof(options));
71+
_dbContextOptions = options.DbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.DbContextOptionsBuilder)}' cannot be null.", nameof(options));
6772
_migrationExecutionStrategy = options.MigrationExecutionStrategy ?? throw new ArgumentException($"The '{nameof(options.MigrationExecutionStrategy)}' cannot be null.", nameof(options));
6873
_testingLoggingOptions = options.TestingLoggingOptions ?? throw new ArgumentException($"The '{nameof(options.TestingLoggingOptions)}' cannot be null.", nameof(options));
6974
_contextInitializations = options.ContextInitializations ?? throw new ArgumentException($"The '{nameof(options.ContextInitializations)}' cannot be null.", nameof(options));
@@ -98,7 +103,14 @@ public T CreateDbContext(bool useMasterConnection)
98103

99104
if (isFirstCtx)
100105
{
101-
_masterConnection.Open();
106+
if (MasterConnection.State != ConnectionState.Open)
107+
{
108+
if (_isExternallyOwnedMasterConnection)
109+
throw new InvalidOperationException("Externally owned connections must be opened by the owner.");
110+
111+
MasterConnection.Open();
112+
}
113+
102114
RunMigrations(ctx);
103115
}
104116

@@ -202,7 +214,9 @@ protected virtual async ValueTask DisposeAsync(bool disposing)
202214
_isAtLeastOneContextCreated = false;
203215
}
204216

205-
await _masterConnection.DisposeAsync();
217+
if (!_isExternallyOwnedMasterConnection)
218+
await MasterConnection.DisposeAsync();
219+
206220
_testingLoggingOptions.Dispose();
207221
}
208222

0 commit comments

Comments
 (0)