1+ public class SqlInstanceProvider < TDbContext > : IAsyncDisposable
2+ where TDbContext : DbContext
3+ {
4+ readonly TemplateFromContext < TDbContext > ? buildTemplate ;
5+ readonly ConstructInstance < TDbContext > constructInstance ;
6+
7+ SemaphoreSlim semaphore = new ( 1 ) ;
8+ SqlInstance < TDbContext > ? sqlInstance ;
9+ MsSqlContainer ? sqlContainer ;
10+
11+ public SqlInstanceProvider ( ConstructInstance < TDbContext > constructInstance , TemplateFromContext < TDbContext > ? buildTemplate = null , string ? storageSuffix = null )
12+ {
13+ this . buildTemplate = buildTemplate ;
14+ this . constructInstance = constructInstance ;
15+ if ( string . Equals ( Environment . GetEnvironmentVariable ( "VERIFY_ENTITYFRAMEWORK_TESTS_SQLENGINE" ) , "Docker" , StringComparison . OrdinalIgnoreCase ) || ! OperatingSystem . IsWindows ( ) )
16+ {
17+ var suffix = storageSuffix == null ? "" : $ ".{ storageSuffix } ";
18+ sqlContainer = new MsSqlBuilder ( "mcr.microsoft.com/mssql/server:2025-latest" )
19+ . WithName ( $ "Verify.EntityFramework.Tests{ suffix } ")
20+ . WithReuse ( true )
21+ . Build ( ) ;
22+ }
23+ else
24+ {
25+ sqlInstance = new (
26+ buildTemplate : buildTemplate ,
27+ storage : storageSuffix == null ? null : Storage . FromSuffix < TDbContext > ( storageSuffix ) ,
28+ constructInstance : constructInstance ) ;
29+ }
30+ }
31+
32+ public async Task < ISqlDatabase < TDbContext > > Build ( IEnumerable < object > data , [ CallerFilePath ] string testFile = "" , string ? databaseSuffix = null , [ CallerMemberName ] string memberName = "" )
33+ {
34+ if ( sqlInstance != null )
35+ {
36+ var sqlDatabase = await sqlInstance . Build ( data , testFile , databaseSuffix , memberName ) ;
37+ return new LocalDbSqlDatabase < TDbContext > ( sqlDatabase ) ;
38+ }
39+
40+ if ( sqlContainer != null )
41+ {
42+ var testClass = Path . GetFileNameWithoutExtension ( testFile ) ;
43+ var dbName = databaseSuffix == null ? $ "{ testClass } _{ memberName } " : $ "{ testClass } _{ memberName } _{ databaseSuffix } ";
44+ var sqlDatabase = await Build ( dbName ) ;
45+ await sqlDatabase . AddData ( data ) ;
46+ return sqlDatabase ;
47+ }
48+
49+ throw new UnreachableException ( "Both sqlInstance and sqlContainer can't be null at the same time" ) ;
50+ }
51+
52+ public async Task < ISqlDatabase < TDbContext > > Build ( [ CallerMemberName ] string dbName = "" )
53+ {
54+ if ( sqlInstance != null )
55+ {
56+ var sqlDatabase = await sqlInstance . Build ( dbName ) ;
57+ return new LocalDbSqlDatabase < TDbContext > ( sqlDatabase ) ;
58+ }
59+
60+ if ( sqlContainer != null )
61+ {
62+ await semaphore . WaitAsync ( ) ;
63+ try
64+ {
65+ if ( sqlContainer . State != TestcontainersStates . Running )
66+ {
67+ await sqlContainer . StartAsync ( ) ;
68+ }
69+ }
70+ finally
71+ {
72+ semaphore . Release ( ) ;
73+ }
74+
75+ var result = await sqlContainer . ExecScriptAsync ( $ "DROP DATABASE IF EXISTS [{ dbName } ]") ;
76+ if ( result . ExitCode != 0 )
77+ {
78+ throw new InvalidOperationException ( $ "Failed to drop database '{ dbName } ' ({ result . ExitCode } )\n { result . Stdout } \n { result . Stderr } ") ;
79+ }
80+
81+ await using var dbContext = CreateDbContext ( dbName ) ;
82+ await dbContext . Database . EnsureCreatedAsync ( ) ;
83+
84+ if ( buildTemplate != null )
85+ {
86+ await buildTemplate ( dbContext ) ;
87+ }
88+
89+ return new ContainerSqlDatabase < TDbContext > ( ( ) => CreateDbContext ( dbName ) ) ;
90+ }
91+
92+ throw new UnreachableException ( "Both sqlInstance and sqlContainer can't be null at the same time" ) ;
93+ }
94+
95+ private TDbContext CreateDbContext ( string dbName )
96+ {
97+ var connectionString = new SqlConnectionStringBuilder ( sqlContainer ! . GetConnectionString ( ) ) { InitialCatalog = dbName } . ConnectionString ;
98+ return constructInstance ( new DbContextOptionsBuilder < TDbContext > ( ) . UseSqlServer ( connectionString ) ) ;
99+ }
100+
101+ public ValueTask DisposeAsync ( )
102+ {
103+ semaphore . Dispose ( ) ;
104+ sqlInstance ? . Dispose ( ) ;
105+ return sqlContainer ? . DisposeAsync ( ) ?? ValueTask . CompletedTask ;
106+ }
107+ }
0 commit comments