22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Diagnostics . CodeAnalysis ;
5+ using Microsoft . EntityFrameworkCore . Diagnostics ;
56using Microsoft . EntityFrameworkCore . Internal ;
7+ using Microsoft . EntityFrameworkCore . Storage ;
68
79namespace Microsoft . EntityFrameworkCore . Migrations . Design ;
810
@@ -34,6 +36,11 @@ public class RuntimeMigrationService : IRuntimeMigrationService
3436 private readonly IMigrator _migrator ;
3537 private readonly IMigrationsSqlGenerator _sqlGenerator ;
3638 private readonly IRelationalDatabaseCreator _databaseCreator ;
39+ private readonly IMigrationCommandExecutor _commandExecutor ;
40+ private readonly IRelationalConnection _connection ;
41+ private readonly IHistoryRepository _historyRepository ;
42+ private readonly IRawSqlCommandBuilder _rawSqlCommandBuilder ;
43+ private readonly IRelationalCommandDiagnosticsLogger _commandLogger ;
3744
3845 /// <summary>
3946 /// Initializes a new instance of the <see cref="RuntimeMigrationService" /> class.
@@ -45,14 +52,24 @@ public class RuntimeMigrationService : IRuntimeMigrationService
4552 /// <param name="migrator">The migrator.</param>
4653 /// <param name="sqlGenerator">The SQL generator.</param>
4754 /// <param name="databaseCreator">The database creator.</param>
55+ /// <param name="commandExecutor">The migration command executor.</param>
56+ /// <param name="connection">The relational connection.</param>
57+ /// <param name="historyRepository">The history repository.</param>
58+ /// <param name="rawSqlCommandBuilder">The raw SQL command builder.</param>
59+ /// <param name="commandLogger">The command logger.</param>
4860 public RuntimeMigrationService (
4961 ICurrentDbContext currentContext ,
5062 IMigrationsScaffolder scaffolder ,
5163 IMigrationCompiler compiler ,
5264 IDynamicMigrationsAssembly dynamicMigrationsAssembly ,
5365 IMigrator migrator ,
5466 IMigrationsSqlGenerator sqlGenerator ,
55- IRelationalDatabaseCreator databaseCreator )
67+ IRelationalDatabaseCreator databaseCreator ,
68+ IMigrationCommandExecutor commandExecutor ,
69+ IRelationalConnection connection ,
70+ IHistoryRepository historyRepository ,
71+ IRawSqlCommandBuilder rawSqlCommandBuilder ,
72+ IRelationalCommandDiagnosticsLogger commandLogger )
5673 {
5774 _currentContext = currentContext ;
5875 _scaffolder = scaffolder ;
@@ -61,6 +78,11 @@ public RuntimeMigrationService(
6178 _migrator = migrator ;
6279 _sqlGenerator = sqlGenerator ;
6380 _databaseCreator = databaseCreator ;
81+ _commandExecutor = commandExecutor ;
82+ _connection = connection ;
83+ _historyRepository = historyRepository ;
84+ _rawSqlCommandBuilder = rawSqlCommandBuilder ;
85+ _commandLogger = commandLogger ;
6486 }
6587
6688 /// <inheritdoc />
@@ -113,7 +135,7 @@ public virtual RuntimeMigrationResult CreateAndApplyMigration(
113135 var applied = false ;
114136 if ( ! options . DryRun )
115137 {
116- _migrator . Migrate ( compiledMigration . MigrationId ) ;
138+ ApplyMigration ( compiledMigration , sqlCommands ) ;
117139 applied = true ;
118140 }
119141
@@ -177,7 +199,7 @@ public virtual async Task<RuntimeMigrationResult> CreateAndApplyMigrationAsync(
177199 var applied = false ;
178200 if ( ! options . DryRun )
179201 {
180- await _migrator . MigrateAsync ( compiledMigration . MigrationId , cancellationToken )
202+ await ApplyMigrationAsync ( compiledMigration , sqlCommands , cancellationToken )
181203 . ConfigureAwait ( false ) ;
182204 applied = true ;
183205 }
@@ -191,6 +213,104 @@ await _migrator.MigrateAsync(compiledMigration.MigrationId, cancellationToken)
191213 savedFiles ? . SnapshotFile ) ;
192214 }
193215
216+ /// <summary>
217+ /// Applies a compiled migration to the database.
218+ /// </summary>
219+ /// <param name="compiledMigration">The compiled migration to apply.</param>
220+ /// <param name="sqlCommands">The pre-generated SQL commands.</param>
221+ protected virtual void ApplyMigration ( CompiledMigration compiledMigration , IReadOnlyList < string > sqlCommands )
222+ {
223+ // Ensure database exists
224+ if ( ! _databaseCreator . Exists ( ) )
225+ {
226+ _databaseCreator . Create ( ) ;
227+ }
228+
229+ _connection . Open ( ) ;
230+ try
231+ {
232+ // Ensure history table exists
233+ if ( ! _historyRepository . Exists ( ) )
234+ {
235+ _historyRepository . Create ( ) ;
236+ }
237+
238+ // Get the migration and its operations
239+ var migration = _dynamicMigrationsAssembly . CreateMigration (
240+ compiledMigration . MigrationTypeInfo ,
241+ _currentContext . Context . Database . ProviderName ! ) ;
242+
243+ // Build the history insert command
244+ var insertCommand = _rawSqlCommandBuilder . Build (
245+ _historyRepository . GetInsertScript ( new HistoryRow ( compiledMigration . MigrationId , ProductInfo . GetVersion ( ) ) ) ) ;
246+
247+ // Generate migration commands and append history insert
248+ var migrationCommands = _sqlGenerator . Generate (
249+ migration . UpOperations ,
250+ _currentContext . Context . Model ) . ToList ( ) ;
251+ migrationCommands . Add ( new MigrationCommand ( insertCommand , _currentContext . Context , _commandLogger ) ) ;
252+
253+ // Execute all commands
254+ _commandExecutor . ExecuteNonQuery ( migrationCommands , _connection ) ;
255+ }
256+ finally
257+ {
258+ _connection . Close ( ) ;
259+ }
260+ }
261+
262+ /// <summary>
263+ /// Applies a compiled migration to the database asynchronously.
264+ /// </summary>
265+ /// <param name="compiledMigration">The compiled migration to apply.</param>
266+ /// <param name="sqlCommands">The pre-generated SQL commands.</param>
267+ /// <param name="cancellationToken">A token to observe while waiting for the task to complete.</param>
268+ /// <returns>A task representing the asynchronous operation.</returns>
269+ protected virtual async Task ApplyMigrationAsync (
270+ CompiledMigration compiledMigration ,
271+ IReadOnlyList < string > sqlCommands ,
272+ CancellationToken cancellationToken = default )
273+ {
274+ // Ensure database exists
275+ if ( ! await _databaseCreator . ExistsAsync ( cancellationToken ) . ConfigureAwait ( false ) )
276+ {
277+ await _databaseCreator . CreateAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
278+ }
279+
280+ await _connection . OpenAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
281+ try
282+ {
283+ // Ensure history table exists
284+ if ( ! await _historyRepository . ExistsAsync ( cancellationToken ) . ConfigureAwait ( false ) )
285+ {
286+ await _historyRepository . CreateAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
287+ }
288+
289+ // Get the migration and its operations
290+ var migration = _dynamicMigrationsAssembly . CreateMigration (
291+ compiledMigration . MigrationTypeInfo ,
292+ _currentContext . Context . Database . ProviderName ! ) ;
293+
294+ // Build the history insert command
295+ var insertCommand = _rawSqlCommandBuilder . Build (
296+ _historyRepository . GetInsertScript ( new HistoryRow ( compiledMigration . MigrationId , ProductInfo . GetVersion ( ) ) ) ) ;
297+
298+ // Generate migration commands and append history insert
299+ var migrationCommands = _sqlGenerator . Generate (
300+ migration . UpOperations ,
301+ _currentContext . Context . Model ) . ToList ( ) ;
302+ migrationCommands . Add ( new MigrationCommand ( insertCommand , _currentContext . Context , _commandLogger ) ) ;
303+
304+ // Execute all commands
305+ await _commandExecutor . ExecuteNonQueryAsync ( migrationCommands , _connection , cancellationToken )
306+ . ConfigureAwait ( false ) ;
307+ }
308+ finally
309+ {
310+ await _connection . CloseAsync ( ) . ConfigureAwait ( false ) ;
311+ }
312+ }
313+
194314 /// <inheritdoc />
195315 public virtual bool HasPendingModelChanges ( )
196316 => _migrator . HasPendingModelChanges ( ) ;
0 commit comments