diff --git a/src/EFCore.Relational/Diagnostics/CommandEndEventData.cs b/src/EFCore.Relational/Diagnostics/CommandEndEventData.cs index 45fd158b742..e5bc2f8e907 100644 --- a/src/EFCore.Relational/Diagnostics/CommandEndEventData.cs +++ b/src/EFCore.Relational/Diagnostics/CommandEndEventData.cs @@ -19,34 +19,20 @@ public class CommandEndEventData : CommandEventData /// /// The event definition. /// A delegate that generates a log message for this event. - /// - /// The . - /// - /// - /// The method. - /// - /// - /// A correlation ID that identifies the instance being used. - /// - /// - /// A correlation ID that identifies the instance being used. - /// - /// - /// Indicates whether or not the command was executed asynchronously. - /// - /// - /// Indicates whether or not the application allows logging of parameter values. - /// - /// - /// The start time of this event. - /// - /// - /// The duration this event. - /// + /// The . + /// The currently being used, to null if not known. + /// The method. + /// A correlation ID that identifies the instance being used. + /// A correlation ID that identifies the instance being used. + /// Indicates whether or not the command was executed asynchronously. + /// Indicates whether or not the application allows logging of parameter values. + /// The start time of this event. + /// The duration this event. public CommandEndEventData( [NotNull] EventDefinitionBase eventDefinition, [NotNull] Func messageGenerator, [NotNull] DbCommand command, + [CanBeNull] DbContext context, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, @@ -54,7 +40,17 @@ public CommandEndEventData( bool logParameterValues, DateTimeOffset startTime, TimeSpan duration) - : base(eventDefinition, messageGenerator, command, executeMethod, commandId, connectionId, async, logParameterValues, startTime) + : base( + eventDefinition, + messageGenerator, + command, + context, + executeMethod, + commandId, + connectionId, + async, + logParameterValues, + startTime) => Duration = duration; /// diff --git a/src/EFCore.Relational/Diagnostics/CommandErrorEventData.cs b/src/EFCore.Relational/Diagnostics/CommandErrorEventData.cs index 2e0e0a697ce..ccf1e4445d4 100644 --- a/src/EFCore.Relational/Diagnostics/CommandErrorEventData.cs +++ b/src/EFCore.Relational/Diagnostics/CommandErrorEventData.cs @@ -18,37 +18,21 @@ public class CommandErrorEventData : CommandEndEventData, IErrorEventData /// /// The event definition. /// A delegate that generates a log message for this event. - /// - /// The that was executing when it failed. - /// - /// - /// The method that was used to execute the command. - /// - /// - /// A correlation ID that identifies the instance being used. - /// - /// - /// A correlation ID that identifies the instance being used. - /// - /// - /// The exception that was thrown when execution failed. - /// - /// - /// Indicates whether or not the command was executed asynchronously. - /// - /// - /// Indicates whether or not the application allows logging of parameter values. - /// - /// - /// The start time of this event. - /// - /// - /// The duration this event. - /// + /// The that was executing when it failed. + /// The currently being used, to null if not known. + /// The method that was used to execute the command. + /// A correlation ID that identifies the instance being used. + /// A correlation ID that identifies the instance being used. + /// The exception that was thrown when execution failed. + /// Indicates whether or not the command was executed asynchronously. + /// Indicates whether or not the application allows logging of parameter values. + /// The start time of this event. + /// The duration this event. public CommandErrorEventData( [NotNull] EventDefinitionBase eventDefinition, [NotNull] Func messageGenerator, [NotNull] DbCommand command, + [CanBeNull] DbContext context, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, @@ -57,7 +41,18 @@ public CommandErrorEventData( bool logParameterValues, DateTimeOffset startTime, TimeSpan duration) - : base(eventDefinition, messageGenerator, command, executeMethod, commandId, connectionId, async, logParameterValues, startTime, duration) + : base( + eventDefinition, + messageGenerator, + command, + context, + executeMethod, + commandId, + connectionId, + async, + logParameterValues, + startTime, + duration) => Exception = exception; /// diff --git a/src/EFCore.Relational/Diagnostics/CommandEventData.cs b/src/EFCore.Relational/Diagnostics/CommandEventData.cs index 7c9e039453f..8d3158c6ce3 100644 --- a/src/EFCore.Relational/Diagnostics/CommandEventData.cs +++ b/src/EFCore.Relational/Diagnostics/CommandEventData.cs @@ -12,45 +12,33 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics /// The event payload for /// command events. /// - public class CommandEventData : EventData + public class CommandEventData : DbContextEventData { /// /// Constructs the event payload. /// /// The event definition. /// A delegate that generates a log message for this event. - /// - /// The . - /// - /// - /// The method. - /// - /// - /// A correlation ID that identifies the instance being used. - /// - /// - /// A correlation ID that identifies the instance being used. - /// - /// - /// Indicates whether or not the command was executed asynchronously. - /// - /// - /// Indicates whether or not the application allows logging of parameter values. - /// - /// - /// The start time of this event. - /// + /// The . + /// The currently being used, to null if not known. + /// The method. + /// A correlation ID that identifies the instance being used. + /// A correlation ID that identifies the instance being used. + /// Indicates whether or not the command was executed asynchronously. + /// Indicates whether or not the application allows logging of parameter values. + /// The start time of this event. public CommandEventData( [NotNull] EventDefinitionBase eventDefinition, [NotNull] Func messageGenerator, [NotNull] DbCommand command, + [CanBeNull] DbContext context, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, bool async, bool logParameterValues, DateTimeOffset startTime) - : base(eventDefinition, messageGenerator) + : base(eventDefinition, messageGenerator, context) { Command = command; CommandId = commandId; diff --git a/src/EFCore.Relational/Diagnostics/CommandExecutedEventData.cs b/src/EFCore.Relational/Diagnostics/CommandExecutedEventData.cs index d919a19bb52..16899fe8944 100644 --- a/src/EFCore.Relational/Diagnostics/CommandExecutedEventData.cs +++ b/src/EFCore.Relational/Diagnostics/CommandExecutedEventData.cs @@ -18,37 +18,21 @@ public class CommandExecutedEventData : CommandEndEventData /// /// The event definition. /// A delegate that generates a log message for this event. - /// - /// The that was executing when it failed. - /// - /// - /// The method that was used to execute the command. - /// - /// - /// A correlation ID that identifies the instance being used. - /// - /// - /// A correlation ID that identifies the instance being used. - /// - /// - /// The result of executing the operation. - /// - /// - /// Indicates whether or not the command was executed asynchronously. - /// - /// - /// Indicates whether or not the application allows logging of parameter values. - /// - /// - /// The start time of this event. - /// - /// - /// The duration this event. - /// + /// The that was executing when it failed. + /// The currently being used, to null if not known. + /// The method that was used to execute the command. + /// A correlation ID that identifies the instance being used. + /// A correlation ID that identifies the instance being used. + /// The result of executing the operation. + /// Indicates whether or not the command was executed asynchronously. + /// Indicates whether or not the application allows logging of parameter values. + /// The start time of this event. + /// The duration this event. public CommandExecutedEventData( [NotNull] EventDefinitionBase eventDefinition, [NotNull] Func messageGenerator, [NotNull] DbCommand command, + [CanBeNull] DbContext context, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, @@ -57,7 +41,18 @@ public CommandExecutedEventData( bool logParameterValues, DateTimeOffset startTime, TimeSpan duration) - : base(eventDefinition, messageGenerator, command, executeMethod, commandId, connectionId, async, logParameterValues, startTime, duration) + : base( + eventDefinition, + messageGenerator, + command, + context, + executeMethod, + commandId, + connectionId, + async, + logParameterValues, + startTime, + duration) => Result = result; /// diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs index ee9ae454bb8..39b10bb4d34 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs @@ -42,6 +42,7 @@ public static class RelationalLoggerExtensions /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The time that execution began. @@ -49,6 +50,7 @@ public static class RelationalLoggerExtensions public static InterceptionResult? CommandReaderExecuting( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, DateTimeOffset startTime) @@ -66,6 +68,7 @@ public static class RelationalLoggerExtensions var eventData = BroadcastCommandExecuting( diagnostics, command, + context, DbCommandMethod.ExecuteReader, commandId, connectionId, @@ -88,6 +91,7 @@ public static class RelationalLoggerExtensions /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The time that execution began. @@ -95,6 +99,7 @@ public static class RelationalLoggerExtensions public static InterceptionResult? CommandScalarExecuting( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, DateTimeOffset startTime) @@ -112,6 +117,7 @@ public static class RelationalLoggerExtensions var eventData = BroadcastCommandExecuting( diagnostics, command, + context, DbCommandMethod.ExecuteScalar, commandId, connectionId, @@ -134,6 +140,7 @@ public static class RelationalLoggerExtensions /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The time that execution began. @@ -141,6 +148,7 @@ public static class RelationalLoggerExtensions public static InterceptionResult? CommandNonQueryExecuting( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, DateTimeOffset startTime) @@ -158,6 +166,7 @@ public static class RelationalLoggerExtensions var eventData = BroadcastCommandExecuting( diagnostics, command, + context, DbCommandMethod.ExecuteNonQuery, commandId, connectionId, @@ -180,6 +189,7 @@ public static class RelationalLoggerExtensions /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The time that execution began. @@ -188,6 +198,7 @@ public static class RelationalLoggerExtensions public static Task?> CommandReaderExecutingAsync( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, DateTimeOffset startTime, @@ -206,6 +217,7 @@ public static class RelationalLoggerExtensions var eventData = BroadcastCommandExecuting( diagnostics, command, + context, DbCommandMethod.ExecuteReader, commandId, connectionId, @@ -228,6 +240,7 @@ public static class RelationalLoggerExtensions /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The time that execution began. @@ -236,6 +249,7 @@ public static class RelationalLoggerExtensions public static Task?> CommandScalarExecutingAsync( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, DateTimeOffset startTime, @@ -254,6 +268,7 @@ public static class RelationalLoggerExtensions var eventData = BroadcastCommandExecuting( diagnostics, command, + context, DbCommandMethod.ExecuteScalar, commandId, connectionId, @@ -276,6 +291,7 @@ public static class RelationalLoggerExtensions /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The time that execution began. @@ -284,6 +300,7 @@ public static class RelationalLoggerExtensions public static Task?> CommandNonQueryExecutingAsync( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, DateTimeOffset startTime, @@ -302,6 +319,7 @@ public static class RelationalLoggerExtensions var eventData = BroadcastCommandExecuting( diagnostics, command, + context, DbCommandMethod.ExecuteNonQuery, commandId, connectionId, @@ -322,6 +340,7 @@ public static class RelationalLoggerExtensions private static CommandEventData BroadcastCommandExecuting( IDiagnosticsLogger diagnostics, DbCommand command, + DbContext context, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, @@ -334,6 +353,7 @@ private static CommandEventData BroadcastCommandExecuting( definition, CommandExecuting, command, + context, executeMethod, commandId, connectionId, @@ -393,6 +413,7 @@ private static bool ShouldLogParameterValues( /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The return value from the underlying method execution. @@ -402,6 +423,7 @@ private static bool ShouldLogParameterValues( public static DbDataReader CommandReaderExecuted( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, [CanBeNull] DbDataReader methodResult, @@ -421,6 +443,7 @@ public static DbDataReader CommandReaderExecuted( var eventData = BroadcastCommandExecuted( diagnostics, command, + context, DbCommandMethod.ExecuteReader, commandId, connectionId, @@ -445,6 +468,7 @@ public static DbDataReader CommandReaderExecuted( /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The return value from the underlying method execution. @@ -454,6 +478,7 @@ public static DbDataReader CommandReaderExecuted( public static object CommandScalarExecuted( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, [CanBeNull] object methodResult, @@ -473,6 +498,7 @@ public static object CommandScalarExecuted( var eventData = BroadcastCommandExecuted( diagnostics, command, + context, DbCommandMethod.ExecuteScalar, commandId, connectionId, @@ -497,6 +523,7 @@ public static object CommandScalarExecuted( /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The return value from the underlying method execution. @@ -506,6 +533,7 @@ public static object CommandScalarExecuted( public static int CommandNonQueryExecuted( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, int methodResult, @@ -525,6 +553,7 @@ public static int CommandNonQueryExecuted( var eventData = BroadcastCommandExecuted( diagnostics, command, + context, DbCommandMethod.ExecuteNonQuery, commandId, connectionId, @@ -549,6 +578,7 @@ public static int CommandNonQueryExecuted( /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The return value from the underlying method execution. @@ -559,6 +589,7 @@ public static int CommandNonQueryExecuted( public static Task CommandReaderExecutedAsync( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, [CanBeNull] DbDataReader methodResult, @@ -579,6 +610,7 @@ public static Task CommandReaderExecutedAsync( var eventData = BroadcastCommandExecuted( diagnostics, command, + context, DbCommandMethod.ExecuteReader, commandId, connectionId, @@ -603,6 +635,7 @@ public static Task CommandReaderExecutedAsync( /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The return value from the underlying method execution. @@ -613,6 +646,7 @@ public static Task CommandReaderExecutedAsync( public static Task CommandScalarExecutedAsync( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, [CanBeNull] object methodResult, @@ -633,6 +667,7 @@ public static Task CommandScalarExecutedAsync( var eventData = BroadcastCommandExecuted( diagnostics, command, + context, DbCommandMethod.ExecuteScalar, commandId, connectionId, @@ -657,6 +692,7 @@ public static Task CommandScalarExecutedAsync( /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. /// The return value from the underlying method execution. @@ -667,6 +703,7 @@ public static Task CommandScalarExecutedAsync( public static Task CommandNonQueryExecutedAsync( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, Guid commandId, Guid connectionId, int methodResult, @@ -687,6 +724,7 @@ public static Task CommandNonQueryExecutedAsync( var eventData = BroadcastCommandExecuted( diagnostics, command, + context, DbCommandMethod.ExecuteNonQuery, commandId, connectionId, @@ -709,6 +747,7 @@ public static Task CommandNonQueryExecutedAsync( private static CommandExecutedEventData BroadcastCommandExecuted( IDiagnosticsLogger diagnostics, DbCommand command, + DbContext context, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, @@ -723,6 +762,7 @@ private static CommandExecutedEventData BroadcastCommandExecuted( definition, CommandExecuted, command, + context, executeMethod, commandId, connectionId, @@ -781,6 +821,7 @@ private static string CommandExecuted(EventDefinitionBase definition, EventData /// /// The diagnostics logger to use. /// The database command object. + /// The currently being used, to null if not known. /// Represents the method that will be called to execute the command. /// The correlation ID associated with the given . /// The correlation ID associated with the being used. @@ -791,6 +832,7 @@ private static string CommandExecuted(EventDefinitionBase definition, EventData public static void CommandError( [NotNull] this IDiagnosticsLogger diagnostics, [NotNull] DbCommand command, + [CanBeNull] DbContext context, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, @@ -823,6 +865,7 @@ public static void CommandError( definition, CommandError, command, + context, executeMethod, commandId, connectionId, diff --git a/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs index 4ce417943f8..11b2a3bc919 100644 --- a/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs @@ -208,21 +208,22 @@ public static int ExecuteSqlCommand( Check.NotNull(sql, nameof(sql)); Check.NotNull(parameters, nameof(parameters)); - var concurrencyDetector = databaseFacade.GetService(); - var logger = databaseFacade.GetService>(); + var concurrencyDetector = GetFacadeDependencies(databaseFacade).ConcurrencyDetector; + var logger = GetFacadeDependencies(databaseFacade).CommandLogger; using (concurrencyDetector.EnterCriticalSection()) { - var rawSqlCommand = databaseFacade - .GetRelationalService() + var rawSqlCommand = GetFacadeDependencies(databaseFacade).RawSqlCommandBuilder .Build(sql.Format, parameters); return rawSqlCommand .RelationalCommand .ExecuteNonQuery( - databaseFacade.GetRelationalService(), - rawSqlCommand.ParameterValues, - logger); + new RelationalCommandParameterObject( + GetFacadeDependencies(databaseFacade).RelationalConnection, + rawSqlCommand.ParameterValues, + ((IDatabaseFacadeDependenciesAccessor)databaseFacade).Context, + logger)); } } @@ -372,21 +373,23 @@ public static async Task ExecuteSqlCommandAsync( Check.NotNull(sql, nameof(sql)); Check.NotNull(parameters, nameof(parameters)); - var concurrencyDetector = databaseFacade.GetService(); - var logger = databaseFacade.GetService>(); + var facadeDependencies = GetFacadeDependencies(databaseFacade); + var concurrencyDetector = facadeDependencies.ConcurrencyDetector; + var logger = facadeDependencies.CommandLogger; using (await concurrencyDetector.EnterCriticalSectionAsync(cancellationToken)) { - var rawSqlCommand = databaseFacade - .GetRelationalService() + var rawSqlCommand = GetFacadeDependencies(databaseFacade).RawSqlCommandBuilder .Build(sql.Format, parameters); return await rawSqlCommand .RelationalCommand .ExecuteNonQueryAsync( - databaseFacade.GetRelationalService(), - rawSqlCommand.ParameterValues, - logger, + new RelationalCommandParameterObject( + facadeDependencies.RelationalConnection, + rawSqlCommand.ParameterValues, + ((IDatabaseFacadeDependenciesAccessor)databaseFacade).Context, + logger), cancellationToken); } } @@ -486,21 +489,23 @@ public static int ExecuteSqlRaw( Check.NotNull(sql, nameof(sql)); Check.NotNull(parameters, nameof(parameters)); - var concurrencyDetector = databaseFacade.GetService(); - var logger = databaseFacade.GetService>(); + var facadeDependencies = GetFacadeDependencies(databaseFacade); + var concurrencyDetector = facadeDependencies.ConcurrencyDetector; + var logger = facadeDependencies.CommandLogger; using (concurrencyDetector.EnterCriticalSection()) { - var rawSqlCommand = databaseFacade - .GetRelationalService() + var rawSqlCommand = GetFacadeDependencies(databaseFacade).RawSqlCommandBuilder .Build(sql, parameters); return rawSqlCommand .RelationalCommand .ExecuteNonQuery( - databaseFacade.GetRelationalService(), - rawSqlCommand.ParameterValues, - logger); + new RelationalCommandParameterObject( + facadeDependencies.RelationalConnection, + rawSqlCommand.ParameterValues, + ((IDatabaseFacadeDependenciesAccessor)databaseFacade).Context, + logger)); } } @@ -636,21 +641,23 @@ public static async Task ExecuteSqlRawAsync( Check.NotNull(sql, nameof(sql)); Check.NotNull(parameters, nameof(parameters)); - var concurrencyDetector = databaseFacade.GetService(); - var logger = databaseFacade.GetService>(); + var facadeDependencies = GetFacadeDependencies(databaseFacade); + var concurrencyDetector = facadeDependencies.ConcurrencyDetector; + var logger = facadeDependencies.CommandLogger; using (await concurrencyDetector.EnterCriticalSectionAsync(cancellationToken)) { - var rawSqlCommand = databaseFacade - .GetRelationalService() + var rawSqlCommand = GetFacadeDependencies(databaseFacade).RawSqlCommandBuilder .Build(sql, parameters); return await rawSqlCommand .RelationalCommand .ExecuteNonQueryAsync( - databaseFacade.GetRelationalService(), - rawSqlCommand.ParameterValues, - logger, + new RelationalCommandParameterObject( + facadeDependencies.RelationalConnection, + rawSqlCommand.ParameterValues, + ((IDatabaseFacadeDependenciesAccessor)databaseFacade).Context, + logger), cancellationToken); } } @@ -661,7 +668,7 @@ public static async Task ExecuteSqlRawAsync( /// The for the context. /// The public static DbConnection GetDbConnection([NotNull] this DatabaseFacade databaseFacade) - => databaseFacade.GetRelationalService().DbConnection; + => GetFacadeDependencies(databaseFacade).RelationalConnection.DbConnection; /// /// Opens the underlying . @@ -670,7 +677,7 @@ public static DbConnection GetDbConnection([NotNull] this DatabaseFacade databas public static void OpenConnection([NotNull] this DatabaseFacade databaseFacade) => databaseFacade.CreateExecutionStrategy().Execute( databaseFacade, database - => database.GetRelationalService().Open()); + => GetFacadeDependencies(database).RelationalConnection.Open()); /// /// Opens the underlying . @@ -685,14 +692,14 @@ public static Task OpenConnectionAsync( CancellationToken cancellationToken = default) => databaseFacade.CreateExecutionStrategy().ExecuteAsync( databaseFacade, (database, ct) => - database.GetRelationalService().OpenAsync(cancellationToken), cancellationToken); + GetFacadeDependencies(database).RelationalConnection.OpenAsync(cancellationToken), cancellationToken); /// /// Closes the underlying . /// /// The for the context. public static void CloseConnection([NotNull] this DatabaseFacade databaseFacade) - => databaseFacade.GetRelationalService().Close(); + => GetFacadeDependencies(databaseFacade).RelationalConnection.Close(); /// /// Starts a new transaction with a given . @@ -766,7 +773,7 @@ public static IDbContextTransaction UseTransaction( /// The for the context. /// The timeout to use, in seconds. public static void SetCommandTimeout([NotNull] this DatabaseFacade databaseFacade, int? timeout) - => databaseFacade.GetRelationalService().CommandTimeout = timeout; + => GetFacadeDependencies(databaseFacade).RelationalConnection.CommandTimeout = timeout; /// /// @@ -806,7 +813,7 @@ public static void SetCommandTimeout([NotNull] this DatabaseFacade databaseFacad /// The for the context. /// The timeout, in seconds, or null if no timeout has been set. public static int? GetCommandTimeout([NotNull] this DatabaseFacade databaseFacade) - => databaseFacade.GetRelationalService().CommandTimeout; + => GetFacadeDependencies(databaseFacade).RelationalConnection.CommandTimeout; /// /// Generates a script to create all tables for the current model. @@ -817,6 +824,18 @@ public static void SetCommandTimeout([NotNull] this DatabaseFacade databaseFacad public static string GenerateCreateScript([NotNull] this DatabaseFacade databaseFacade) => databaseFacade.GetRelationalService().GenerateCreateScript(); + private static IRelationalDatabaseFacadeDependencies GetFacadeDependencies(DatabaseFacade databaseFacade) + { + var dependencies = ((IDatabaseFacadeDependenciesAccessor)databaseFacade).Dependencies; + + if (dependencies is IRelationalDatabaseFacadeDependencies relationalDependencies) + { + return relationalDependencies; + } + + throw new InvalidOperationException(RelationalStrings.RelationalNotInUse); + } + private static TService GetRelationalService(this IInfrastructure databaseFacade) { Check.NotNull(databaseFacade, nameof(databaseFacade)); @@ -831,6 +850,6 @@ private static TService GetRelationalService(this IInfrastructure Check.NotNull(databaseFacade, nameof(databaseFacade)).GetService(); + => ((IDatabaseFacadeDependenciesAccessor)Check.NotNull(databaseFacade, nameof(databaseFacade))).Dependencies.TransactionManager; } } diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 558f02c3b21..0c2c1dc75bf 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations.Internal; @@ -76,6 +77,7 @@ public static readonly IDictionary RelationalServi { typeof(IMigrationsAssembly), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IBatchExecutor), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IRelationalConnection), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IRelationalDatabaseFacadeDependencies), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IRelationalDatabaseCreator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IHistoryRepository), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(INamedConnectionStringResolver), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -153,6 +155,8 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(p => p.GetService()); + TryAdd(); TryAdd(); TryAdd(p => p.GetService()); diff --git a/src/EFCore.Relational/Internal/IRelationalDatabaseFacadeDependencies.cs b/src/EFCore.Relational/Internal/IRelationalDatabaseFacadeDependencies.cs new file mode 100644 index 00000000000..50a0462c029 --- /dev/null +++ b/src/EFCore.Relational/Internal/IRelationalDatabaseFacadeDependencies.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Internal +{ + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + /// + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. + /// + /// + public interface IRelationalDatabaseFacadeDependencies : IDatabaseFacadeDependencies + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IRelationalConnection RelationalConnection { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IRawSqlCommandBuilder RawSqlCommandBuilder { get; } + } +} diff --git a/src/EFCore.Relational/Internal/RelationalDatabaseFacadeDependencies.cs b/src/EFCore.Relational/Internal/RelationalDatabaseFacadeDependencies.cs new file mode 100644 index 00000000000..75430a7a7df --- /dev/null +++ b/src/EFCore.Relational/Internal/RelationalDatabaseFacadeDependencies.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Internal +{ + public class RelationalDatabaseFacadeDependencies : DatabaseFacadeDependencies, IRelationalDatabaseFacadeDependencies + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public RelationalDatabaseFacadeDependencies( + [NotNull] IDbContextTransactionManager transactionManager, + [NotNull] IDatabaseCreator databaseCreator, + [NotNull] IExecutionStrategyFactory executionStrategyFactory, + [NotNull] IEnumerable databaseProviders, + [NotNull] IDiagnosticsLogger commandLogger, + [NotNull] IConcurrencyDetector concurrencyDetector, + [NotNull] IRelationalConnection relationalConnection, + [NotNull] IRawSqlCommandBuilder rawSqlCommandBuilder) + : base( + transactionManager, + databaseCreator, + executionStrategyFactory, + databaseProviders, + commandLogger, + concurrencyDetector) + { + RelationalConnection = relationalConnection; + RawSqlCommandBuilder = rawSqlCommandBuilder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IRelationalConnection RelationalConnection { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IRawSqlCommandBuilder RawSqlCommandBuilder { get; } + } +} diff --git a/src/EFCore.Relational/Migrations/HistoryRepository.cs b/src/EFCore.Relational/Migrations/HistoryRepository.cs index bb70c124047..9d4ade88ebb 100644 --- a/src/EFCore.Relational/Migrations/HistoryRepository.cs +++ b/src/EFCore.Relational/Migrations/HistoryRepository.cs @@ -132,7 +132,12 @@ protected virtual string ProductVersionColumnName public virtual bool Exists() => Dependencies.DatabaseCreator.Exists() && InterpretExistsResult( - Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalar(Dependencies.Connection, null, Dependencies.CommandLogger)); + Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalar( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + Dependencies.CurrentDbContext.Context, + Dependencies.CommandLogger))); /// /// Checks whether or not the history table exists. @@ -146,7 +151,12 @@ public virtual async Task ExistsAsync(CancellationToken cancellationToken => await Dependencies.DatabaseCreator.ExistsAsync(cancellationToken) && InterpretExistsResult( await Dependencies.RawSqlCommandBuilder.Build(ExistsSql).ExecuteScalarAsync( - Dependencies.Connection, null, Dependencies.CommandLogger, cancellationToken: cancellationToken)); + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + Dependencies.CurrentDbContext.Context, + Dependencies.CommandLogger), + cancellationToken)); /// /// Interprets the result of executing . @@ -204,7 +214,12 @@ public virtual IReadOnlyList GetAppliedMigrations() { var command = Dependencies.RawSqlCommandBuilder.Build(GetAppliedMigrationsSql); - using (var reader = command.ExecuteReader(Dependencies.Connection, null, Dependencies.CommandLogger)) + using (var reader = command.ExecuteReader( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + Dependencies.CurrentDbContext.Context, + Dependencies.CommandLogger))) { while (reader.Read()) { @@ -233,7 +248,13 @@ public virtual async Task> GetAppliedMigrationsAsync( { var command = Dependencies.RawSqlCommandBuilder.Build(GetAppliedMigrationsSql); - using (var reader = await command.ExecuteReaderAsync(Dependencies.Connection, null, Dependencies.CommandLogger, cancellationToken: cancellationToken)) + using (var reader = await command.ExecuteReaderAsync( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + Dependencies.CurrentDbContext.Context, + Dependencies.CommandLogger), + cancellationToken)) { while (await reader.ReadAsync(cancellationToken)) { diff --git a/src/EFCore.Relational/Migrations/HistoryRepositoryDependencies.cs b/src/EFCore.Relational/Migrations/HistoryRepositoryDependencies.cs index 232a5a72057..b484ff5e9f6 100644 --- a/src/EFCore.Relational/Migrations/HistoryRepositoryDependencies.cs +++ b/src/EFCore.Relational/Migrations/HistoryRepositoryDependencies.cs @@ -72,6 +72,7 @@ public sealed class HistoryRepositoryDependencies /// Helpers for generating update SQL. /// The convention set to use when creating the model. /// The type mapper. + /// Contains the currently in use. /// The logger for model building events. /// The command logger. [EntityFrameworkInternal] @@ -85,6 +86,7 @@ public HistoryRepositoryDependencies( [NotNull] ISqlGenerationHelper sqlGenerationHelper, [NotNull] IConventionSetBuilder conventionSetBuilder, [NotNull] IRelationalTypeMappingSource typeMappingSource, + [NotNull] ICurrentDbContext currentDbContext, [NotNull] IDiagnosticsLogger modelLogger, [NotNull] IDiagnosticsLogger commandLogger) { @@ -97,6 +99,7 @@ public HistoryRepositoryDependencies( Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper)); Check.NotNull(conventionSetBuilder, nameof(conventionSetBuilder)); Check.NotNull(typeMappingSource, nameof(typeMappingSource)); + Check.NotNull(currentDbContext, nameof(currentDbContext)); Check.NotNull(modelLogger, nameof(modelLogger)); Check.NotNull(commandLogger, nameof(commandLogger)); @@ -109,6 +112,7 @@ public HistoryRepositoryDependencies( SqlGenerationHelper = sqlGenerationHelper; ConventionSetBuilder = conventionSetBuilder; TypeMappingSource = typeMappingSource; + CurrentDbContext = currentDbContext; ModelLogger = modelLogger; CommandLogger = commandLogger; } @@ -158,6 +162,11 @@ public HistoryRepositoryDependencies( /// public IRelationalTypeMappingSource TypeMappingSource { get; } + /// + /// Contains the currently in use. + /// + public ICurrentDbContext CurrentDbContext { get; } + /// /// The model logger /// @@ -184,6 +193,7 @@ public HistoryRepositoryDependencies With([NotNull] IRelationalDatabaseCreator d SqlGenerationHelper, ConventionSetBuilder, TypeMappingSource, + CurrentDbContext, ModelLogger, CommandLogger); @@ -203,6 +213,7 @@ public HistoryRepositoryDependencies With([NotNull] IRawSqlCommandBuilder rawSql SqlGenerationHelper, ConventionSetBuilder, TypeMappingSource, + CurrentDbContext, ModelLogger, CommandLogger); @@ -222,6 +233,7 @@ public HistoryRepositoryDependencies With([NotNull] IRelationalConnection connec SqlGenerationHelper, ConventionSetBuilder, TypeMappingSource, + CurrentDbContext, ModelLogger, CommandLogger); @@ -241,6 +253,7 @@ public HistoryRepositoryDependencies With([NotNull] IDbContextOptions options) SqlGenerationHelper, ConventionSetBuilder, TypeMappingSource, + CurrentDbContext, ModelLogger, CommandLogger); @@ -260,6 +273,7 @@ public HistoryRepositoryDependencies With([NotNull] IMigrationsModelDiffer model SqlGenerationHelper, ConventionSetBuilder, TypeMappingSource, + CurrentDbContext, ModelLogger, CommandLogger); @@ -279,6 +293,7 @@ public HistoryRepositoryDependencies With([NotNull] IMigrationsSqlGenerator migr SqlGenerationHelper, ConventionSetBuilder, TypeMappingSource, + CurrentDbContext, ModelLogger, CommandLogger); @@ -298,6 +313,7 @@ public HistoryRepositoryDependencies With([NotNull] ISqlGenerationHelper sqlGene sqlGenerationHelper, ConventionSetBuilder, TypeMappingSource, + CurrentDbContext, ModelLogger, CommandLogger); @@ -317,6 +333,7 @@ public HistoryRepositoryDependencies With([NotNull] IConventionSetBuilder conven SqlGenerationHelper, conventionSetBuilder, TypeMappingSource, + CurrentDbContext, ModelLogger, CommandLogger); @@ -336,6 +353,27 @@ public HistoryRepositoryDependencies With([NotNull] IRelationalTypeMappingSource SqlGenerationHelper, ConventionSetBuilder, typeMappingSource, + CurrentDbContext, + ModelLogger, + CommandLogger); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// The type mapper. + /// A new parameter object with the given service replaced. + public HistoryRepositoryDependencies With([NotNull] ICurrentDbContext currentDbContext) + => new HistoryRepositoryDependencies( + DatabaseCreator, + RawSqlCommandBuilder, + Connection, + Options, + ModelDiffer, + MigrationsSqlGenerator, + SqlGenerationHelper, + ConventionSetBuilder, + TypeMappingSource, + currentDbContext, ModelLogger, CommandLogger); @@ -355,6 +393,7 @@ public HistoryRepositoryDependencies With([NotNull] IDiagnosticsLogger _logger; private readonly IDiagnosticsLogger _commandLogger; private readonly string _activeProvider; @@ -59,6 +61,7 @@ public Migrator( [NotNull] IMigrationCommandExecutor migrationCommandExecutor, [NotNull] IRelationalConnection connection, [NotNull] ISqlGenerationHelper sqlGenerationHelper, + [NotNull] ICurrentDbContext currentDbContext, [NotNull] IDiagnosticsLogger logger, [NotNull] IDiagnosticsLogger commandLogger, [NotNull] IDatabaseProvider databaseProvider) @@ -71,6 +74,7 @@ public Migrator( Check.NotNull(migrationCommandExecutor, nameof(migrationCommandExecutor)); Check.NotNull(connection, nameof(connection)); Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper)); + Check.NotNull(currentDbContext, nameof(currentDbContext)); Check.NotNull(logger, nameof(logger)); Check.NotNull(commandLogger, nameof(commandLogger)); Check.NotNull(databaseProvider, nameof(databaseProvider)); @@ -83,6 +87,7 @@ public Migrator( _migrationCommandExecutor = migrationCommandExecutor; _connection = connection; _sqlGenerationHelper = sqlGenerationHelper; + _currentDbContext = currentDbContext; _logger = logger; _commandLogger = commandLogger; _activeProvider = databaseProvider.Name; @@ -108,7 +113,12 @@ public virtual void Migrate(string targetMigration = null) var command = _rawSqlCommandBuilder.Build( _historyRepository.GetCreateScript()); - command.ExecuteNonQuery(_connection, null, _commandLogger); + command.ExecuteNonQuery( + new RelationalCommandParameterObject( + _connection, + null, + _currentDbContext.Context, + _commandLogger)); } var commandLists = GetMigrationCommandLists(_historyRepository.GetAppliedMigrations(), targetMigration); @@ -140,7 +150,13 @@ public virtual async Task MigrateAsync( var command = _rawSqlCommandBuilder.Build( _historyRepository.GetCreateScript()); - await command.ExecuteNonQueryAsync(_connection, null, _commandLogger, cancellationToken: cancellationToken); + await command.ExecuteNonQueryAsync( + new RelationalCommandParameterObject( + _connection, + null, + _currentDbContext.Context, + _commandLogger), + cancellationToken); } var commandLists = GetMigrationCommandLists( @@ -382,7 +398,7 @@ protected virtual IReadOnlyList GenerateUpSql([NotNull] Migrat return _migrationsSqlGenerator .Generate(migration.UpOperations, migration.TargetModel) - .Concat(new[] { new MigrationCommand(insertCommand, _commandLogger) }) + .Concat(new[] { new MigrationCommand(insertCommand, _currentDbContext.Context, _commandLogger) }) .ToList(); } @@ -403,7 +419,7 @@ protected virtual IReadOnlyList GenerateDownSql( return _migrationsSqlGenerator .Generate(migration.DownOperations, previousMigration?.TargetModel) - .Concat(new[] { new MigrationCommand(deleteCommand, _commandLogger) }) + .Concat(new[] { new MigrationCommand(deleteCommand, _currentDbContext.Context, _commandLogger) }) .ToList(); } } diff --git a/src/EFCore.Relational/Migrations/MigrationCommand.cs b/src/EFCore.Relational/Migrations/MigrationCommand.cs index 66f2c8aa4b7..752414b149e 100644 --- a/src/EFCore.Relational/Migrations/MigrationCommand.cs +++ b/src/EFCore.Relational/Migrations/MigrationCommand.cs @@ -17,22 +17,26 @@ namespace Microsoft.EntityFrameworkCore.Migrations public class MigrationCommand { private readonly IRelationalCommand _relationalCommand; + private readonly DbContext _context; private readonly IDiagnosticsLogger _logger; /// /// Creates a new instance of the command. /// /// The underlying that will be used to execute the command. + /// The current or null if not known. /// The command logger. /// Indicates whether or not transactions should be suppressed while executing the command. public MigrationCommand( [NotNull] IRelationalCommand relationalCommand, + [CanBeNull] DbContext context, [NotNull] IDiagnosticsLogger logger, bool transactionSuppressed = false) { Check.NotNull(relationalCommand, nameof(relationalCommand)); _relationalCommand = relationalCommand; + _context = context; _logger = logger; TransactionSuppressed = transactionSuppressed; } @@ -57,9 +61,11 @@ public virtual int ExecuteNonQuery( [NotNull] IRelationalConnection connection, [CanBeNull] IReadOnlyDictionary parameterValues = null) => _relationalCommand.ExecuteNonQuery( - Check.NotNull(connection, nameof(connection)), - parameterValues, - _logger); + new RelationalCommandParameterObject( + connection, + parameterValues, + _context, + _logger)); /// /// Executes the command and returns the number of rows affected. @@ -73,9 +79,11 @@ public virtual Task ExecuteNonQueryAsync( [CanBeNull] IReadOnlyDictionary parameterValues = null, CancellationToken cancellationToken = default) => _relationalCommand.ExecuteNonQueryAsync( - Check.NotNull(connection, nameof(connection)), - parameterValues, - _logger, + new RelationalCommandParameterObject( + connection, + parameterValues, + _context, + _logger), cancellationToken); } } diff --git a/src/EFCore.Relational/Migrations/MigrationCommandListBuilder.cs b/src/EFCore.Relational/Migrations/MigrationCommandListBuilder.cs index b0388be2f02..079d56aae10 100644 --- a/src/EFCore.Relational/Migrations/MigrationCommandListBuilder.cs +++ b/src/EFCore.Relational/Migrations/MigrationCommandListBuilder.cs @@ -51,7 +51,13 @@ public virtual MigrationCommandListBuilder EndCommand(bool suppressTransaction = { if (_commandBuilder.CommandTextLength != 0) { - _commands.Add(new MigrationCommand(_commandBuilder.Build(), _dependencies.Logger, suppressTransaction)); + _commands.Add( + new MigrationCommand( + _commandBuilder.Build(), + _dependencies.CurrentDbContext.Context, + _dependencies.Logger, + suppressTransaction)); + _commandBuilder = _dependencies.CommandBuilderFactory.Create(); } diff --git a/src/EFCore.Relational/Migrations/MigrationsSqlGeneratorDependencies.cs b/src/EFCore.Relational/Migrations/MigrationsSqlGeneratorDependencies.cs index 59f1b7ea95f..e6a7ecb7707 100644 --- a/src/EFCore.Relational/Migrations/MigrationsSqlGeneratorDependencies.cs +++ b/src/EFCore.Relational/Migrations/MigrationsSqlGeneratorDependencies.cs @@ -59,6 +59,7 @@ public sealed class MigrationsSqlGeneratorDependencies /// High level SQL generator. /// Helpers for SQL generation. /// The type mapper. + /// Contains the currently in use. /// A logger. [EntityFrameworkInternal] public MigrationsSqlGeneratorDependencies( @@ -66,18 +67,21 @@ public MigrationsSqlGeneratorDependencies( [NotNull] IUpdateSqlGenerator updateSqlGenerator, [NotNull] ISqlGenerationHelper sqlGenerationHelper, [NotNull] IRelationalTypeMappingSource typeMappingSource, + [NotNull] ICurrentDbContext currentDbContext, [NotNull] IDiagnosticsLogger logger) { Check.NotNull(commandBuilderFactory, nameof(commandBuilderFactory)); Check.NotNull(updateSqlGenerator, nameof(updateSqlGenerator)); Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper)); Check.NotNull(typeMappingSource, nameof(typeMappingSource)); + Check.NotNull(currentDbContext, nameof(currentDbContext)); Check.NotNull(logger, nameof(logger)); CommandBuilderFactory = commandBuilderFactory; SqlGenerationHelper = sqlGenerationHelper; UpdateSqlGenerator = updateSqlGenerator; TypeMappingSource = typeMappingSource; + CurrentDbContext = currentDbContext; Logger = logger; } @@ -101,6 +105,11 @@ public MigrationsSqlGeneratorDependencies( /// public IRelationalTypeMappingSource TypeMappingSource { get; } + /// + /// Contains the currently in use. + /// + public ICurrentDbContext CurrentDbContext { get; } + /// /// A logger. /// @@ -117,6 +126,7 @@ public MigrationsSqlGeneratorDependencies With([NotNull] IRelationalCommandBuild UpdateSqlGenerator, SqlGenerationHelper, TypeMappingSource, + CurrentDbContext, Logger); /// @@ -130,6 +140,7 @@ public MigrationsSqlGeneratorDependencies With([NotNull] IUpdateSqlGenerator upd updateSqlGenerator, SqlGenerationHelper, TypeMappingSource, + CurrentDbContext, Logger); /// @@ -143,6 +154,7 @@ public MigrationsSqlGeneratorDependencies With([NotNull] ISqlGenerationHelper sq UpdateSqlGenerator, sqlGenerationHelper, TypeMappingSource, + CurrentDbContext, Logger); /// @@ -156,6 +168,21 @@ public MigrationsSqlGeneratorDependencies With([NotNull] IRelationalTypeMappingS UpdateSqlGenerator, SqlGenerationHelper, typeMappingSource, + CurrentDbContext, + Logger); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public MigrationsSqlGeneratorDependencies With([NotNull] ICurrentDbContext currentDbContext) + => new MigrationsSqlGeneratorDependencies( + CommandBuilderFactory, + UpdateSqlGenerator, + SqlGenerationHelper, + TypeMappingSource, + currentDbContext, Logger); /// @@ -169,6 +196,7 @@ public MigrationsSqlGeneratorDependencies With([NotNull] IDiagnosticsLogger MoveNextAsync() _dataReader = await relationalCommand.ExecuteReaderAsync( - _relationalQueryContext.Connection, - _relationalQueryContext.ParameterValues, - _relationalQueryContext.CommandLogger, + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger), _cancellationToken); if (selectExpression.IsNonComposedFromSql()) diff --git a/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs b/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs index f4ba2951745..69806e6746a 100644 --- a/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs +++ b/src/EFCore.Relational/Query/Pipeline/QueryingEnumerable.cs @@ -93,9 +93,11 @@ public bool MoveNext() _dataReader = relationalCommand.ExecuteReader( - _relationalQueryContext.Connection, - _relationalQueryContext.ParameterValues, - _relationalQueryContext.CommandLogger); + new RelationalCommandParameterObject( + _relationalQueryContext.Connection, + _relationalQueryContext.ParameterValues, + _relationalQueryContext.Context, + _relationalQueryContext.CommandLogger)); if (selectExpression.IsNonComposedFromSql()) { diff --git a/src/EFCore.Relational/Storage/IRelationalCommand.cs b/src/EFCore.Relational/Storage/IRelationalCommand.cs index ba4fd262880..f508fb367c6 100644 --- a/src/EFCore.Relational/Storage/IRelationalCommand.cs +++ b/src/EFCore.Relational/Storage/IRelationalCommand.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Diagnostics; namespace Microsoft.EntityFrameworkCore.Storage { @@ -33,85 +31,58 @@ public interface IRelationalCommand /// /// Executes the command with no results. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// The number of rows affected. - int ExecuteNonQuery( - [NotNull] IRelationalConnection connection, - [CanBeNull] IReadOnlyDictionary parameterValues, - [CanBeNull] IDiagnosticsLogger logger); + int ExecuteNonQuery(RelationalCommandParameterObject parameterObject); /// /// Asynchronously executes the command with no results. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// A to observe while waiting for the task to complete. /// /// A task that represents the asynchronous operation. The task result contains the number of rows affected. /// Task ExecuteNonQueryAsync( - [NotNull] IRelationalConnection connection, - [CanBeNull] IReadOnlyDictionary parameterValues, - [CanBeNull] IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default); /// /// Executes the command with a single scalar result. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// The result of the command. - object ExecuteScalar( - [NotNull] IRelationalConnection connection, - [CanBeNull] IReadOnlyDictionary parameterValues, - [CanBeNull] IDiagnosticsLogger logger); + object ExecuteScalar(RelationalCommandParameterObject parameterObject); /// /// Asynchronously executes the command with a single scalar result. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// A to observe while waiting for the task to complete. /// /// A task that represents the asynchronous operation. The task result contains the result of the command. /// Task ExecuteScalarAsync( - [NotNull] IRelationalConnection connection, - [CanBeNull] IReadOnlyDictionary parameterValues, - [CanBeNull] IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default); /// /// Executes the command with a result. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// The result of the command. - RelationalDataReader ExecuteReader( - [NotNull] IRelationalConnection connection, - [CanBeNull] IReadOnlyDictionary parameterValues, - [CanBeNull] IDiagnosticsLogger logger); + RelationalDataReader ExecuteReader(RelationalCommandParameterObject parameterObject); /// /// Asynchronously executes the command with a result. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// A to observe while waiting for the task to complete. /// /// A task that represents the asynchronous operation. The task result contains the result of the command. /// Task ExecuteReaderAsync( - [NotNull] IRelationalConnection connection, - [CanBeNull] IReadOnlyDictionary parameterValues, - [NotNull] IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default); } } diff --git a/src/EFCore.Relational/Storage/RelationalCommand.cs b/src/EFCore.Relational/Storage/RelationalCommand.cs index 6f477571c65..7b74a909781 100644 --- a/src/EFCore.Relational/Storage/RelationalCommand.cs +++ b/src/EFCore.Relational/Storage/RelationalCommand.cs @@ -26,7 +26,7 @@ public class RelationalCommand : IRelationalCommand { /// /// - /// Constructs a new . + /// Constructs a new . /// /// /// This type is typically used by database providers (and other extensions). It is generally @@ -68,223 +68,331 @@ public RelationalCommand( /// /// Executes the command with no results. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// The number of rows affected. - public virtual int ExecuteNonQuery( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) - => (int)Execute( - Check.NotNull(connection, nameof(connection)), - DbCommandMethod.ExecuteNonQuery, - parameterValues, - logger); + public virtual int ExecuteNonQuery(RelationalCommandParameterObject parameterObject) + { + var (connection, context, logger) = (parameterObject.Connection, parameterObject.Context, parameterObject.Logger); + + var command = CreateCommand(connection, parameterObject.ParameterValues); + + connection.Open(); + + var commandId = Guid.NewGuid(); + var startTime = DateTimeOffset.UtcNow; + var stopwatch = Stopwatch.StartNew(); + try + { + var nonQueryResult = (logger?.CommandNonQueryExecuting( + command, + context, + commandId, + connection.ConnectionId, + startTime: startTime) + ?? new InterceptionResult(command.ExecuteNonQuery())).Result; + + return logger?.CommandNonQueryExecuted( + command, + context, + commandId, + connection.ConnectionId, + nonQueryResult, + startTime, + stopwatch.Elapsed) + ?? nonQueryResult; + } + catch (Exception exception) + { + logger?.CommandError( + command, + context, + DbCommandMethod.ExecuteNonQuery, + commandId, + connection.ConnectionId, + exception, + false, + startTime, + stopwatch.Elapsed); + + throw; + } + finally + { + CleanupCommand(command, connection); + } + } + + private static void CleanupCommand( + DbCommand dbCommand, + IRelationalConnection connection) + { + dbCommand.Parameters.Clear(); + dbCommand.Dispose(); + connection.Close(); + } /// /// Asynchronously executes the command with no results. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// A to observe while waiting for the task to complete. /// /// A task that represents the asynchronous operation. The task result contains the number of rows affected. /// - public virtual Task ExecuteNonQueryAsync( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + public virtual async Task ExecuteNonQueryAsync( + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) - => ExecuteAsync( - Check.NotNull(connection, nameof(connection)), - DbCommandMethod.ExecuteNonQuery, - parameterValues, - logger, - cancellationToken).Cast(); + { + var (connection, context, logger) = (parameterObject.Connection, parameterObject.Context, parameterObject.Logger); + + var command = CreateCommand(connection, parameterObject.ParameterValues); + + await connection.OpenAsync(cancellationToken); + + var commandId = Guid.NewGuid(); + + var startTime = DateTimeOffset.UtcNow; + var stopwatch = Stopwatch.StartNew(); + try + { + var interceptionResult = logger == null + ? null + : await logger.CommandNonQueryExecutingAsync( + command, + context, + commandId, + connection.ConnectionId, + startTime, + cancellationToken); + + var result = interceptionResult.HasValue + ? interceptionResult.Value.Result + : await command.ExecuteNonQueryAsync(cancellationToken); + + if (logger != null) + { + result = await logger.CommandNonQueryExecutedAsync( + command, + context, + commandId, + connection.ConnectionId, + result, + startTime, + stopwatch.Elapsed, + cancellationToken); + } + + return result; + } + catch (Exception exception) + { + logger?.CommandError( + command, + context, + DbCommandMethod.ExecuteNonQuery, + commandId, + connection.ConnectionId, + exception, + true, + startTime, + stopwatch.Elapsed); + + throw; + } + finally + { + CleanupCommand(command, connection); + } + } /// /// Executes the command with a single scalar result. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// The result of the command. - public virtual object ExecuteScalar( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) - => Execute( - Check.NotNull(connection, nameof(connection)), - DbCommandMethod.ExecuteScalar, - parameterValues, - logger); + public virtual object ExecuteScalar(RelationalCommandParameterObject parameterObject) + { + var (connection, context, logger) = (parameterObject.Connection, parameterObject.Context, parameterObject.Logger); + + var command = CreateCommand(connection, parameterObject.ParameterValues); + + connection.Open(); + + var commandId = Guid.NewGuid(); + var startTime = DateTimeOffset.UtcNow; + var stopwatch = Stopwatch.StartNew(); + try + { + var result = (logger?.CommandScalarExecuting( + command, + context, + commandId, + connection.ConnectionId, + startTime) + ?? new InterceptionResult(command.ExecuteScalar())).Result; + + return logger?.CommandScalarExecuted( + command, + context, + commandId, + connection.ConnectionId, + result, + startTime, + stopwatch.Elapsed) + ?? result; + } + catch (Exception exception) + { + logger?.CommandError( + command, + context, + DbCommandMethod.ExecuteScalar, + commandId, + connection.ConnectionId, + exception, + false, + startTime, + stopwatch.Elapsed); + + throw; + } + finally + { + CleanupCommand(command, connection); + } + } /// /// Asynchronously executes the command with a single scalar result. /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. + /// Parameters for this method. /// A to observe while waiting for the task to complete. /// /// A task that represents the asynchronous operation. The task result contains the result of the command. /// - public virtual Task ExecuteScalarAsync( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + public virtual async Task ExecuteScalarAsync( + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) - => ExecuteAsync( - Check.NotNull(connection, nameof(connection)), - DbCommandMethod.ExecuteScalar, - parameterValues, - logger, - cancellationToken); + { + var (connection, context, logger) = (parameterObject.Connection, parameterObject.Context, parameterObject.Logger); - /// - /// Executes the command with a result. - /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. - /// The result of the command. - public virtual RelationalDataReader ExecuteReader( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) - => (RelationalDataReader)Execute( - Check.NotNull(connection, nameof(connection)), - DbCommandMethod.ExecuteReader, - parameterValues, - logger); + var command = CreateCommand(connection, parameterObject.ParameterValues); - /// - /// Asynchronously executes the command with a result. - /// - /// The connection to execute against. - /// The values for the parameters. - /// The command logger. - /// A to observe while waiting for the task to complete. - /// - /// A task that represents the asynchronous operation. The task result contains the result of the command. - /// - public virtual Task ExecuteReaderAsync( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, - CancellationToken cancellationToken = default) - => ExecuteAsync( - Check.NotNull(connection, nameof(connection)), - DbCommandMethod.ExecuteReader, - parameterValues, - logger, - cancellationToken).Cast(); + await connection.OpenAsync(cancellationToken); + + var commandId = Guid.NewGuid(); + var startTime = DateTimeOffset.UtcNow; + var stopwatch = Stopwatch.StartNew(); + + try + { + var interceptionResult = logger == null + ? null + : await logger.CommandScalarExecutingAsync( + command, + context, + commandId, + connection.ConnectionId, + startTime, + cancellationToken); + + var result = interceptionResult.HasValue + ? interceptionResult.Value.Result + : await command.ExecuteScalarAsync(cancellationToken); + + if (logger != null) + { + result = await logger.CommandScalarExecutedAsync( + command, + context, + commandId, + connection.ConnectionId, + result, + startTime, + stopwatch.Elapsed, + cancellationToken); + } + + return result; + } + catch (Exception exception) + { + logger?.CommandError( + command, + context, + DbCommandMethod.ExecuteScalar, + commandId, + connection.ConnectionId, + exception, + true, + startTime, + stopwatch.Elapsed); + + throw; + } + finally + { + CleanupCommand(command, connection); + } + } /// - /// The method called by other methods on this type to execute synchronously. + /// Executes the command with a result. /// - /// The connection to use. - /// The method type. - /// The parameter values. - /// The command logger. - /// The result of the execution. - protected virtual object Execute( - [NotNull] IRelationalConnection connection, - DbCommandMethod executeMethod, - [CanBeNull] IReadOnlyDictionary parameterValues, - [CanBeNull] IDiagnosticsLogger logger) + /// Parameters for this method. + /// The result of the command. + public virtual RelationalDataReader ExecuteReader(RelationalCommandParameterObject parameterObject) { - Check.NotNull(connection, nameof(connection)); + var (connection, context, logger) = (parameterObject.Connection, parameterObject.Context, parameterObject.Logger); - var dbCommand = CreateCommand(connection, parameterValues); + var command = CreateCommand(connection, parameterObject.ParameterValues); connection.Open(); var commandId = Guid.NewGuid(); - var startTime = DateTimeOffset.UtcNow; var stopwatch = Stopwatch.StartNew(); - object result; var readerOpen = false; try { - switch (executeMethod) + var reader = (logger?.CommandReaderExecuting( + command, + context, + commandId, + connection.ConnectionId, + startTime) + ?? new InterceptionResult(command.ExecuteReader())).Result; + + if (logger != null) { - case DbCommandMethod.ExecuteNonQuery: - var nonQueryResult = (logger?.CommandNonQueryExecuting( - dbCommand, - commandId, - connection.ConnectionId, - startTime: startTime) - ?? new InterceptionResult(dbCommand.ExecuteNonQuery())).Result; - - result = logger?.CommandNonQueryExecuted( - dbCommand, - commandId, - connection.ConnectionId, - nonQueryResult, - startTime, - stopwatch.Elapsed) - ?? nonQueryResult; - - break; - case DbCommandMethod.ExecuteScalar: - var scalarResult = (logger?.CommandScalarExecuting( - dbCommand, - commandId, - connection.ConnectionId, - startTime: startTime) - ?? new InterceptionResult(dbCommand.ExecuteScalar())).Result; - - result = logger?.CommandScalarExecuted( - dbCommand, - commandId, - connection.ConnectionId, - scalarResult, - startTime, - stopwatch.Elapsed) - ?? scalarResult; - break; - case DbCommandMethod.ExecuteReader: - var reader = (logger?.CommandReaderExecuting( - dbCommand, - commandId, - connection.ConnectionId, - startTime: startTime) - ?? new InterceptionResult(dbCommand.ExecuteReader())).Result; - - if (logger != null) - { - reader = logger?.CommandReaderExecuted( - dbCommand, - commandId, - connection.ConnectionId, - reader, - startTime, - stopwatch.Elapsed); - } - - result = new RelationalDataReader( - connection, - dbCommand, - reader, - commandId, - logger); - - readerOpen = true; - break; - default: - throw new NotSupportedException(); + reader = logger.CommandReaderExecuted( + command, + context, + commandId, + connection.ConnectionId, + reader, + startTime, + stopwatch.Elapsed); } + + var result = new RelationalDataReader( + connection, + command, + reader, + commandId, + logger); + + readerOpen = true; + + return result; } catch (Exception exception) { logger?.CommandError( - dbCommand, - executeMethod, + command, + context, + DbCommandMethod.ExecuteReader, commandId, connection.ConnectionId, exception, @@ -298,149 +406,80 @@ protected virtual object Execute( { if (!readerOpen) { - dbCommand.Parameters.Clear(); - dbCommand.Dispose(); - connection.Close(); + CleanupCommand(command, connection); } } - - return result; } /// - /// The method called by other methods on this type to execute synchronously. + /// Asynchronously executes the command with a result. /// - /// The connection to use. - /// The method type. - /// The parameter values. - /// The command logger. - /// The cancellation token. - /// The result of the execution. - protected virtual async Task ExecuteAsync( - [NotNull] IRelationalConnection connection, - DbCommandMethod executeMethod, - [CanBeNull] IReadOnlyDictionary parameterValues, - [CanBeNull] IDiagnosticsLogger logger, + /// Parameters for this method. + /// A to observe while waiting for the task to complete. + /// + /// A task that represents the asynchronous operation. The task result contains the result of the command. + /// + public virtual async Task ExecuteReaderAsync( + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) { - Check.NotNull(connection, nameof(connection)); + var (connection, context, logger) = (parameterObject.Connection, parameterObject.Context, parameterObject.Logger); - var dbCommand = CreateCommand(connection, parameterValues); + var command = CreateCommand(connection, parameterObject.ParameterValues); await connection.OpenAsync(cancellationToken); var commandId = Guid.NewGuid(); - var startTime = DateTimeOffset.UtcNow; var stopwatch = Stopwatch.StartNew(); - object result; var readerOpen = false; try { - switch (executeMethod) + var interceptionResult = logger == null + ? null + : await logger.CommandReaderExecutingAsync( + command, + context, + commandId, + connection.ConnectionId, + startTime, + cancellationToken); + + var reader = interceptionResult.HasValue + ? interceptionResult.Value.Result + : await command.ExecuteReaderAsync(cancellationToken); + + if (logger != null) { - case DbCommandMethod.ExecuteNonQuery: - var nonQueryResult = logger == null - ? null - : await logger.CommandNonQueryExecutingAsync( - dbCommand, - commandId, - connection.ConnectionId, - startTime: startTime, - cancellationToken); - - var nonQueryValue = nonQueryResult.HasValue - ? nonQueryResult.Value.Result - : await dbCommand.ExecuteNonQueryAsync(cancellationToken); - - if (logger != null) - { - nonQueryValue = await logger.CommandNonQueryExecutedAsync( - dbCommand, - commandId, - connection.ConnectionId, - nonQueryValue, - startTime, - stopwatch.Elapsed, - cancellationToken); - } - - result = nonQueryValue; - break; - case DbCommandMethod.ExecuteScalar: - var scalarResult = logger == null - ? null - : await logger.CommandScalarExecutingAsync( - dbCommand, - commandId, - connection.ConnectionId, - startTime: startTime, - cancellationToken); - - var scalarValue = scalarResult.HasValue - ? scalarResult.Value.Result - : await dbCommand.ExecuteScalarAsync(cancellationToken); - - if (logger != null) - { - scalarValue = await logger.CommandScalarExecutedAsync( - dbCommand, - commandId, - connection.ConnectionId, - scalarValue, - startTime, - stopwatch.Elapsed, - cancellationToken); - } - - result = scalarValue; - break; - case DbCommandMethod.ExecuteReader: - var readerResult = logger == null - ? null - : await logger.CommandReaderExecutingAsync( - dbCommand, - commandId, - connection.ConnectionId, - startTime: startTime, - cancellationToken); - - var reader = readerResult.HasValue - ? readerResult.Value.Result - : await dbCommand.ExecuteReaderAsync(cancellationToken); - - if (logger != null) - { - reader = await logger.CommandReaderExecutedAsync( - dbCommand, - commandId, - connection.ConnectionId, - reader, - startTime, - stopwatch.Elapsed, - cancellationToken); - } - - readerOpen = true; - - result = new RelationalDataReader( - connection, - dbCommand, - reader, - commandId, - logger); - - break; - default: - throw new NotSupportedException(); + reader = await logger.CommandReaderExecutedAsync( + command, + context, + commandId, + connection.ConnectionId, + reader, + startTime, + stopwatch.Elapsed, + cancellationToken); } + + var result = new RelationalDataReader( + connection, + command, + reader, + commandId, + logger); + + readerOpen = true; + + return result; } catch (Exception exception) { logger?.CommandError( - dbCommand, - executeMethod, + command, + context, + DbCommandMethod.ExecuteReader, commandId, connection.ConnectionId, exception, @@ -454,19 +493,15 @@ protected virtual async Task ExecuteAsync( { if (!readerOpen) { - dbCommand.Parameters.Clear(); - dbCommand.Dispose(); - connection.Close(); + CleanupCommand(command, connection); } } - - return result; } /// /// - /// Template method called by amd to - /// create a for the given and configure + /// Template method called by the execute methods to + /// create a for the given and configure /// timeouts and transactions. /// /// @@ -479,8 +514,10 @@ protected virtual async Task ExecuteAsync( /// The created command. protected virtual DbCommand CreateCommand( [NotNull] IRelationalConnection connection, - [NotNull] IReadOnlyDictionary parameterValues) + [CanBeNull] IReadOnlyDictionary parameterValues) { + Check.NotNull(connection, nameof(connection)); + var command = connection.DbConnection.CreateCommand(); command.CommandText = CommandText; @@ -495,7 +532,8 @@ protected virtual DbCommand CreateCommand( command.CommandTimeout = (int)connection.CommandTimeout; } - if (Parameters.Count > 0) + if (Parameters != null + && Parameters.Count > 0) { if (parameterValues == null) { @@ -504,9 +542,9 @@ protected virtual DbCommand CreateCommand( Parameters[0].InvariantName)); } - foreach (var parameter in Parameters) + for (var i = 0; i < Parameters.Count; i++) { - parameter.AddDbParameter(command, parameterValues); + Parameters[i].AddDbParameter(command, parameterValues); } } diff --git a/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs b/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs new file mode 100644 index 00000000000..ab4a049f524 --- /dev/null +++ b/src/EFCore.Relational/Storage/RelationalCommandParameterObject.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Storage +{ + /// + /// + /// A parameter object for the execution methods on . + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + public readonly struct RelationalCommandParameterObject + { + /// + /// + /// Creates a new parameter object for the given parameters. + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The connection on which the command will execute. + /// The SQL parameter values to use, or null if none. + /// The current instance, or null if it is not known. + /// A logger, or null if no logger is available. + public RelationalCommandParameterObject( + [NotNull] IRelationalConnection connection, + [CanBeNull] IReadOnlyDictionary parameterValues, + [CanBeNull] DbContext context, + [CanBeNull] IDiagnosticsLogger logger) + { + Check.NotNull(connection, nameof(connection)); + + Connection = connection; + ParameterValues = parameterValues; + Context = context; + Logger = logger; + } + + /// + /// The connection on which the command will execute. + /// + public IRelationalConnection Connection { get; } + + /// + /// The SQL parameter values to use, or null if none. + /// + public IReadOnlyDictionary ParameterValues { get; } + + /// + /// The current instance, or null if it is not known. + /// + public DbContext Context { get; } + + /// + /// A logger, or null if no logger is available. + /// + public IDiagnosticsLogger Logger { get; } + } +} diff --git a/src/EFCore.Relational/Storage/RelationalDatabaseCreatorDependencies.cs b/src/EFCore.Relational/Storage/RelationalDatabaseCreatorDependencies.cs index a3c7b0ad85e..b291768839d 100644 --- a/src/EFCore.Relational/Storage/RelationalDatabaseCreatorDependencies.cs +++ b/src/EFCore.Relational/Storage/RelationalDatabaseCreatorDependencies.cs @@ -62,6 +62,7 @@ public sealed class RelationalDatabaseCreatorDependencies /// The to be used. /// The to be used. /// The to be used. + /// Contains the currently in use. /// The command logger. [EntityFrameworkInternal] public RelationalDatabaseCreatorDependencies( @@ -72,6 +73,7 @@ public RelationalDatabaseCreatorDependencies( [NotNull] IMigrationCommandExecutor migrationCommandExecutor, [NotNull] ISqlGenerationHelper sqlGenerationHelper, [NotNull] IExecutionStrategyFactory executionStrategyFactory, + [NotNull] ICurrentDbContext currentDbContext, [NotNull] IDiagnosticsLogger commandLogger) { Check.NotNull(model, nameof(model)); @@ -81,6 +83,7 @@ public RelationalDatabaseCreatorDependencies( Check.NotNull(migrationCommandExecutor, nameof(migrationCommandExecutor)); Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper)); Check.NotNull(executionStrategyFactory, nameof(executionStrategyFactory)); + Check.NotNull(currentDbContext, nameof(currentDbContext)); Check.NotNull(commandLogger, nameof(commandLogger)); Model = model; @@ -90,6 +93,7 @@ public RelationalDatabaseCreatorDependencies( MigrationCommandExecutor = migrationCommandExecutor; SqlGenerationHelper = sqlGenerationHelper; ExecutionStrategyFactory = executionStrategyFactory; + CurrentDbContext = currentDbContext; CommandLogger = commandLogger; } @@ -133,6 +137,11 @@ public RelationalDatabaseCreatorDependencies( /// public IDiagnosticsLogger CommandLogger { get; } + /// + /// Contains the currently in use. + /// + public ICurrentDbContext CurrentDbContext { get; } + /// /// Clones this dependency parameter object with one service replaced. /// @@ -147,6 +156,7 @@ public RelationalDatabaseCreatorDependencies With([NotNull] IModel model) MigrationCommandExecutor, SqlGenerationHelper, ExecutionStrategyFactory, + CurrentDbContext, CommandLogger); /// @@ -163,6 +173,7 @@ public RelationalDatabaseCreatorDependencies With([NotNull] IRelationalConnectio MigrationCommandExecutor, SqlGenerationHelper, ExecutionStrategyFactory, + CurrentDbContext, CommandLogger); /// @@ -179,6 +190,7 @@ public RelationalDatabaseCreatorDependencies With([NotNull] IMigrationsModelDiff MigrationCommandExecutor, SqlGenerationHelper, ExecutionStrategyFactory, + CurrentDbContext, CommandLogger); /// @@ -195,6 +207,7 @@ public RelationalDatabaseCreatorDependencies With([NotNull] IMigrationsSqlGenera MigrationCommandExecutor, SqlGenerationHelper, ExecutionStrategyFactory, + CurrentDbContext, CommandLogger); /// @@ -211,6 +224,7 @@ public RelationalDatabaseCreatorDependencies With([NotNull] IMigrationCommandExe migrationCommandExecutor, SqlGenerationHelper, ExecutionStrategyFactory, + CurrentDbContext, CommandLogger); /// @@ -227,6 +241,7 @@ public RelationalDatabaseCreatorDependencies With([NotNull] ISqlGenerationHelper MigrationCommandExecutor, sqlGenerationHelper, ExecutionStrategyFactory, + CurrentDbContext, CommandLogger); /// @@ -243,6 +258,24 @@ public RelationalDatabaseCreatorDependencies With([NotNull] IExecutionStrategyFa MigrationCommandExecutor, SqlGenerationHelper, executionStrategyFactory, + CurrentDbContext, + CommandLogger); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public RelationalDatabaseCreatorDependencies With([NotNull] ICurrentDbContext currentDbContext) + => new RelationalDatabaseCreatorDependencies( + Model, + Connection, + ModelDiffer, + MigrationsSqlGenerator, + MigrationCommandExecutor, + SqlGenerationHelper, + ExecutionStrategyFactory, + currentDbContext, CommandLogger); /// @@ -259,6 +292,7 @@ public RelationalDatabaseCreatorDependencies With([NotNull] IDiagnosticsLogger The command builder factory. /// The sql generator. /// The update generator. + /// Contains the currently in use. /// A logger. public ModificationCommandBatchFactoryDependencies( [NotNull] IRelationalCommandBuilderFactory commandBuilderFactory, [NotNull] ISqlGenerationHelper sqlGenerationHelper, [NotNull] IUpdateSqlGenerator updateSqlGenerator, [NotNull] IRelationalValueBufferFactoryFactory valueBufferFactoryFactory, + [NotNull] ICurrentDbContext currentDbContext, [NotNull] IDiagnosticsLogger logger) { Check.NotNull(commandBuilderFactory, nameof(commandBuilderFactory)); @@ -69,6 +72,7 @@ public ModificationCommandBatchFactoryDependencies( SqlGenerationHelper = sqlGenerationHelper; UpdateSqlGenerator = updateSqlGenerator; ValueBufferFactoryFactory = valueBufferFactoryFactory; + CurrentDbContext = currentDbContext; Logger = logger; } @@ -97,6 +101,11 @@ public ModificationCommandBatchFactoryDependencies( /// public IRelationalValueBufferFactoryFactory ValueBufferFactoryFactory { get; } + /// + /// Contains the currently in use. + /// + public ICurrentDbContext CurrentDbContext { get; } + /// /// Clones this dependency parameter object with one service replaced. /// @@ -108,6 +117,7 @@ public ModificationCommandBatchFactoryDependencies With([NotNull] IDiagnosticsLo SqlGenerationHelper, UpdateSqlGenerator, ValueBufferFactoryFactory, + CurrentDbContext, logger); /// @@ -121,6 +131,7 @@ public ModificationCommandBatchFactoryDependencies With([NotNull] IRelationalVal SqlGenerationHelper, UpdateSqlGenerator, valueBufferFactoryFactory, + CurrentDbContext, Logger); /// @@ -134,6 +145,7 @@ public ModificationCommandBatchFactoryDependencies With([NotNull] IRelationalCom SqlGenerationHelper, UpdateSqlGenerator, ValueBufferFactoryFactory, + CurrentDbContext, Logger); /// @@ -147,6 +159,7 @@ public ModificationCommandBatchFactoryDependencies With([NotNull] ISqlGeneration sqlGenerationHelper, UpdateSqlGenerator, ValueBufferFactoryFactory, + CurrentDbContext, Logger); /// @@ -160,6 +173,21 @@ public ModificationCommandBatchFactoryDependencies With([NotNull] IUpdateSqlGene SqlGenerationHelper, updateSqlGenerator, ValueBufferFactoryFactory, + CurrentDbContext, + Logger); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public ModificationCommandBatchFactoryDependencies With([NotNull] ICurrentDbContext currentDbContext) + => new ModificationCommandBatchFactoryDependencies( + CommandBuilderFactory, + SqlGenerationHelper, + UpdateSqlGenerator, + ValueBufferFactoryFactory, + currentDbContext, Logger); } } diff --git a/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs b/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs index 1c3860c4819..b25bd61e543 100644 --- a/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs +++ b/src/EFCore.Relational/Update/ReaderModificationCommandBatch.cs @@ -25,8 +25,6 @@ namespace Microsoft.EntityFrameworkCore.Update /// public abstract class ReaderModificationCommandBatch : ModificationCommandBatch { - private readonly IRelationalCommandBuilderFactory _commandBuilderFactory; - private readonly IRelationalValueBufferFactoryFactory _valueBufferFactoryFactory; private readonly List _modificationCommands = new List(); /// @@ -37,37 +35,28 @@ protected ReaderModificationCommandBatch([NotNull] ModificationCommandBatchFacto { Check.NotNull(dependencies, nameof(dependencies)); - _commandBuilderFactory = dependencies.CommandBuilderFactory; - SqlGenerationHelper = dependencies.SqlGenerationHelper; - UpdateSqlGenerator = dependencies.UpdateSqlGenerator; - _valueBufferFactoryFactory = dependencies.ValueBufferFactoryFactory; - Logger = dependencies.Logger; + Dependencies = dependencies; } /// - /// Gets or sets the cached command text for the commands in the batch. + /// Service dependencies. /// - protected virtual StringBuilder CachedCommandText { get; [param: NotNull] set; } + public virtual ModificationCommandBatchFactoryDependencies Dependencies { get; } /// - /// The ordinal of the last command for which command text was built. + /// The update SQL generator. /// - protected virtual int LastCachedCommandIndex { get; set; } + protected virtual IUpdateSqlGenerator UpdateSqlGenerator => Dependencies.UpdateSqlGenerator; /// - /// A helper for SQL generation. - /// - protected virtual ISqlGenerationHelper SqlGenerationHelper { get; } - - /// - /// A SQL generator for insert, update, and delete commands. + /// Gets or sets the cached command text for the commands in the batch. /// - protected virtual IUpdateSqlGenerator UpdateSqlGenerator { get; } + protected virtual StringBuilder CachedCommandText { get; [param: NotNull] set; } /// - /// A logger. + /// The ordinal of the last command for which command text was built. /// - protected virtual IDiagnosticsLogger Logger { get; } + protected virtual int LastCachedCommandIndex { get; set; } /// /// The list of conceptual insert/update/delete s in the batch. @@ -194,7 +183,7 @@ protected virtual int GetParameterCount() /// The command. protected virtual RawSqlCommand CreateStoreCommand() { - var commandBuilder = _commandBuilderFactory + var commandBuilder = Dependencies.CommandBuilderFactory .Create() .Append(GetCommandText()); @@ -212,7 +201,7 @@ protected virtual RawSqlCommand CreateStoreCommand() { commandBuilder.AddParameter( columnModification.ParameterName, - SqlGenerationHelper.GenerateParameterName(columnModification.ParameterName), + Dependencies.SqlGenerationHelper.GenerateParameterName(columnModification.ParameterName), columnModification.Property); parameterValues.Add(columnModification.ParameterName, columnModification.Value); @@ -222,7 +211,7 @@ protected virtual RawSqlCommand CreateStoreCommand() { commandBuilder.AddParameter( columnModification.OriginalParameterName, - SqlGenerationHelper.GenerateParameterName(columnModification.OriginalParameterName), + Dependencies.SqlGenerationHelper.GenerateParameterName(columnModification.OriginalParameterName), columnModification.Property); parameterValues.Add(columnModification.OriginalParameterName, columnModification.OriginalValue); @@ -247,9 +236,11 @@ public override void Execute(IRelationalConnection connection) try { using (var dataReader = storeCommand.RelationalCommand.ExecuteReader( - connection, - storeCommand.ParameterValues, - Logger)) + new RelationalCommandParameterObject( + connection, + storeCommand.ParameterValues, + Dependencies.CurrentDbContext.Context, + Dependencies.Logger))) { Consume(dataReader); } @@ -282,9 +273,11 @@ public override async Task ExecuteAsync( try { using (var dataReader = await storeCommand.RelationalCommand.ExecuteReaderAsync( - connection, - storeCommand.ParameterValues, - Logger, + new RelationalCommandParameterObject( + connection, + storeCommand.ParameterValues, + Dependencies.CurrentDbContext.Context, + Dependencies.Logger), cancellationToken)) { await ConsumeAsync(dataReader, cancellationToken); @@ -326,7 +319,7 @@ protected abstract Task ConsumeAsync( /// /// The factory. protected virtual IRelationalValueBufferFactory CreateValueBufferFactory([NotNull] IReadOnlyList columnModifications) - => _valueBufferFactoryFactory + => Dependencies.ValueBufferFactoryFactory .Create( Check.NotNull(columnModifications, nameof(columnModifications)) .Where(c => c.IsRead) diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs index 557aae7da40..41ecf151122 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerDatabaseCreator.cs @@ -118,9 +118,11 @@ public override bool HasTables() _connection, connection => (int)CreateHasTablesCommand() .ExecuteScalar( - connection, - null, - Dependencies.CommandLogger) != 0); + new RelationalCommandParameterObject( + connection, + null, + Dependencies.CurrentDbContext.Context, + Dependencies.CommandLogger)) != 0); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -133,9 +135,11 @@ public override Task HasTablesAsync(CancellationToken cancellationToken = _connection, async (connection, ct) => (int)await CreateHasTablesCommand() .ExecuteScalarAsync( - connection, - null, - Dependencies.CommandLogger, + new RelationalCommandParameterObject( + connection, + null, + Dependencies.CurrentDbContext.Context, + Dependencies.CommandLogger), cancellationToken: ct) != 0, cancellationToken); private IRelationalCommand CreateHasTablesCommand() diff --git a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceHiLoValueGenerator.cs b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceHiLoValueGenerator.cs index 761e0bdcafb..4e7030c4626 100644 --- a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceHiLoValueGenerator.cs +++ b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerSequenceHiLoValueGenerator.cs @@ -60,7 +60,12 @@ protected override long GetNewLowValue() => (long)Convert.ChangeType( _rawSqlCommandBuilder .Build(_sqlGenerator.GenerateNextSequenceValueOperation(_sequence.Name, _sequence.Schema)) - .ExecuteScalar(_connection, null, _commandLogger), + .ExecuteScalar( + new RelationalCommandParameterObject( + _connection, + null, + null, + _commandLogger)), typeof(long), CultureInfo.InvariantCulture); @@ -74,7 +79,13 @@ protected override async Task GetNewLowValueAsync(CancellationToken cancel => (long)Convert.ChangeType( await _rawSqlCommandBuilder .Build(_sqlGenerator.GenerateNextSequenceValueOperation(_sequence.Name, _sequence.Schema)) - .ExecuteScalarAsync(_connection, null, _commandLogger, cancellationToken: cancellationToken), + .ExecuteScalarAsync( + new RelationalCommandParameterObject( + _connection, + null, + null, + _commandLogger), + cancellationToken), typeof(long), CultureInfo.InvariantCulture); diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteDatabaseCreator.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteDatabaseCreator.cs index e5869db01a5..1ffcd9ddc94 100644 --- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteDatabaseCreator.cs +++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteDatabaseCreator.cs @@ -58,7 +58,12 @@ public override void Create() Dependencies.Connection.Open(); _rawSqlCommandBuilder.Build("PRAGMA journal_mode = 'wal';") - .ExecuteNonQuery(Dependencies.Connection, null, Dependencies.CommandLogger); + .ExecuteNonQuery( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + null, + Dependencies.CommandLogger)); Dependencies.Connection.Close(); } @@ -96,7 +101,12 @@ public override bool HasTables() { var count = (long)_rawSqlCommandBuilder .Build("SELECT COUNT(*) FROM \"sqlite_master\" WHERE \"type\" = 'table' AND \"rootpage\" IS NOT NULL;") - .ExecuteScalar(Dependencies.Connection, null, Dependencies.CommandLogger); + .ExecuteScalar( + new RelationalCommandParameterObject( + Dependencies.Connection, + null, + null, + Dependencies.CommandLogger)); return count != 0; } diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs index c8e31f5de37..33940692e4a 100644 --- a/src/EFCore/DbContext.cs +++ b/src/EFCore/DbContext.cs @@ -118,7 +118,7 @@ public virtual DatabaseFacade Database { CheckDisposed(); - return _database ?? (_database = new DatabaseFacade(this)); + return _database ??= new DatabaseFacade(this); } } @@ -126,8 +126,7 @@ public virtual DatabaseFacade Database /// Provides access to information and operations for entity instances this context is tracking. /// public virtual ChangeTracker ChangeTracker - => _changeTracker - ?? (_changeTracker = InternalServiceProvider.GetRequiredService().Create()); + => _changeTracker ??= InternalServiceProvider.GetRequiredService().Create(); /// /// The metadata about the shape of entities, the relationships between them, and how they map to the database. @@ -336,7 +335,7 @@ private IDbContextDependencies DbContextDependencies { CheckDisposed(); - return _dbContextDependencies ?? (_dbContextDependencies = InternalServiceProvider.GetRequiredService()); + return _dbContextDependencies ??= InternalServiceProvider.GetRequiredService(); } } diff --git a/src/EFCore/Diagnostics/DbContextEventData.cs b/src/EFCore/Diagnostics/DbContextEventData.cs index 7d122f7b579..4bc779ff9b7 100644 --- a/src/EFCore/Diagnostics/DbContextEventData.cs +++ b/src/EFCore/Diagnostics/DbContextEventData.cs @@ -18,11 +18,11 @@ public class DbContextEventData : EventData /// /// The event definition. /// A delegate that generates a log message for this event. - /// The current . + /// The current , or null if not known. public DbContextEventData( [NotNull] EventDefinitionBase eventDefinition, [NotNull] Func messageGenerator, - [NotNull] DbContext context) + [CanBeNull] DbContext context) : base(eventDefinition, messageGenerator) { Context = context; diff --git a/src/EFCore/Extensions/TransactionsDatabaseFacadeExtensions.cs b/src/EFCore/Extensions/TransactionsDatabaseFacadeExtensions.cs index 380c7a0cfb0..021c147c1e5 100644 --- a/src/EFCore/Extensions/TransactionsDatabaseFacadeExtensions.cs +++ b/src/EFCore/Extensions/TransactionsDatabaseFacadeExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; @@ -25,7 +26,7 @@ public static class TransactionsDatabaseFacadeExtensions public static void EnlistTransaction([NotNull] this DatabaseFacade databaseFacade, [CanBeNull] Transaction transaction) { Check.NotNull(databaseFacade, nameof(databaseFacade)); - if (databaseFacade.GetService() is ITransactionEnlistmentManager transactionManager) + if (((IDatabaseFacadeDependenciesAccessor)databaseFacade).Dependencies.TransactionManager is ITransactionEnlistmentManager transactionManager) { transactionManager.EnlistTransaction(transaction); } @@ -43,7 +44,7 @@ public static void EnlistTransaction([NotNull] this DatabaseFacade databaseFacad public static Transaction GetEnlistedTransaction([NotNull] this DatabaseFacade databaseFacade) { Check.NotNull(databaseFacade, nameof(databaseFacade)); - if (databaseFacade.GetService() is ITransactionEnlistmentManager transactionManager) + if (((IDatabaseFacadeDependenciesAccessor)databaseFacade).Dependencies.TransactionManager is ITransactionEnlistmentManager transactionManager) { return transactionManager.EnlistedTransaction; } diff --git a/src/EFCore/Infrastructure/DatabaseFacade.cs b/src/EFCore/Infrastructure/DatabaseFacade.cs index bcdbe4d7156..4c08d88552e 100644 --- a/src/EFCore/Infrastructure/DatabaseFacade.cs +++ b/src/EFCore/Infrastructure/DatabaseFacade.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; @@ -18,12 +19,10 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure /// Instances of this class are typically obtained from and it is not designed /// to be directly constructed in your application code. /// - public class DatabaseFacade : IInfrastructure + public class DatabaseFacade : IInfrastructure, IDatabaseFacadeDependenciesAccessor { private readonly DbContext _context; - private IDatabaseCreator _databaseCreator; - private IDbContextTransactionManager _transactionManager; - private IExecutionStrategyFactory _executionStrategyFactory; + private IDatabaseFacadeDependencies _dependencies; /// /// Initializes a new instance of the class. Instances of this class are typically @@ -38,6 +37,9 @@ public DatabaseFacade([NotNull] DbContext context) _context = context; } + private IDatabaseFacadeDependencies Dependencies + => _dependencies ??= _context.GetService(); + /// /// /// Ensures that the database for the context exists. If it exists, no action is taken. If it does not @@ -52,7 +54,7 @@ public DatabaseFacade([NotNull] DbContext context) /// /// /// True if the database is created, false if it already existed. - public virtual bool EnsureCreated() => DatabaseCreator.EnsureCreated(); + public virtual bool EnsureCreated() => Dependencies.DatabaseCreator.EnsureCreated(); /// /// @@ -73,7 +75,7 @@ public DatabaseFacade([NotNull] DbContext context) /// false if it already existed. /// public virtual Task EnsureCreatedAsync(CancellationToken cancellationToken = default) - => DatabaseCreator.EnsureCreatedAsync(cancellationToken); + => Dependencies.DatabaseCreator.EnsureCreatedAsync(cancellationToken); /// /// @@ -86,7 +88,7 @@ public virtual Task EnsureCreatedAsync(CancellationToken cancellationToken /// /// /// True if the database is deleted, false if it did not exist. - public virtual bool EnsureDeleted() => DatabaseCreator.EnsureDeleted(); + public virtual bool EnsureDeleted() => Dependencies.DatabaseCreator.EnsureDeleted(); /// /// @@ -104,7 +106,7 @@ public virtual Task EnsureCreatedAsync(CancellationToken cancellationToken /// false if it did not exist. /// public virtual Task EnsureDeletedAsync(CancellationToken cancellationToken = default) - => DatabaseCreator.EnsureDeletedAsync(cancellationToken); + => Dependencies.DatabaseCreator.EnsureDeletedAsync(cancellationToken); /// /// @@ -117,7 +119,7 @@ public virtual Task EnsureDeletedAsync(CancellationToken cancellationToken /// /// True if the database is available; false otherwise. public virtual bool CanConnect() - => DatabaseCreator.CanConnect(); + => Dependencies.DatabaseCreator.CanConnect(); /// /// @@ -131,7 +133,7 @@ public virtual bool CanConnect() /// A to observe while waiting for the task to complete. /// True if the database is available; false otherwise. public virtual Task CanConnectAsync(CancellationToken cancellationToken = default) - => DatabaseCreator.CanConnectAsync(cancellationToken); + => Dependencies.DatabaseCreator.CanConnectAsync(cancellationToken); /// /// Starts a new transaction. @@ -140,7 +142,7 @@ public virtual Task CanConnectAsync(CancellationToken cancellationToken = /// A that represents the started transaction. /// public virtual IDbContextTransaction BeginTransaction() - => TransactionManager.BeginTransaction(); + => Dependencies.TransactionManager.BeginTransaction(); /// /// Asynchronously starts a new transaction. @@ -151,26 +153,26 @@ public virtual IDbContextTransaction BeginTransaction() /// that represents the started transaction. /// public virtual Task BeginTransactionAsync(CancellationToken cancellationToken = default) - => TransactionManager.BeginTransactionAsync(cancellationToken); + => Dependencies.TransactionManager.BeginTransactionAsync(cancellationToken); /// /// Applies the outstanding operations in the current transaction to the database. /// public virtual void CommitTransaction() - => TransactionManager.CommitTransaction(); + => Dependencies.TransactionManager.CommitTransaction(); /// /// Discards the outstanding operations in the current transaction. /// public virtual void RollbackTransaction() - => TransactionManager.RollbackTransaction(); + => Dependencies.TransactionManager.RollbackTransaction(); /// /// Creates an instance of the configured . /// /// An instance. public virtual IExecutionStrategy CreateExecutionStrategy() - => ExecutionStrategyFactory.Create(); + => Dependencies.ExecutionStrategyFactory.Create(); /// /// @@ -189,7 +191,7 @@ public virtual IExecutionStrategy CreateExecutionStrategy() /// /// public virtual IDbContextTransaction CurrentTransaction - => TransactionManager.CurrentTransaction; + => Dependencies.TransactionManager.CurrentTransaction; /// /// @@ -227,6 +229,7 @@ public virtual IDbContextTransaction CurrentTransaction /// /// public virtual string ProviderName + // Needs to be lazy because used from OnModelCreating => _context.GetService>() ?.Select(p => p.Name) .FirstOrDefault(); @@ -242,14 +245,23 @@ public virtual string ProviderName /// IServiceProvider IInfrastructure.Instance => ((IInfrastructure)_context).Instance; - private IDbContextTransactionManager TransactionManager - => _transactionManager ?? (_transactionManager = this.GetService()); - - private IDatabaseCreator DatabaseCreator - => _databaseCreator ?? (_databaseCreator = this.GetService()); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IDatabaseFacadeDependencies IDatabaseFacadeDependenciesAccessor.Dependencies + => Dependencies; - private IExecutionStrategyFactory ExecutionStrategyFactory - => _executionStrategyFactory ?? (_executionStrategyFactory = this.GetService()); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + DbContext IDatabaseFacadeDependenciesAccessor.Context + => _context; #region Hidden System.Object members diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index e2cbcf94b12..3743a7eea50 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -118,6 +118,7 @@ public static readonly IDictionary CoreServices { typeof(IUpdateAdapterFactory), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(ICurrentDbContext), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IDbContextDependencies), new ServiceCharacteristics(ServiceLifetime.Scoped) }, + { typeof(IDatabaseFacadeDependencies), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IDbContextOptions), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IDatabase), new ServiceCharacteristics(ServiceLifetime.Scoped) }, { typeof(IDatabaseCreator), new ServiceCharacteristics(ServiceLifetime.Scoped) }, @@ -228,6 +229,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(); TryAdd(); TryAdd(); TryAdd(); diff --git a/src/EFCore/Internal/DatabaseFacadeDependencies.cs b/src/EFCore/Internal/DatabaseFacadeDependencies.cs new file mode 100644 index 00000000000..15752f6993f --- /dev/null +++ b/src/EFCore/Internal/DatabaseFacadeDependencies.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Internal +{ + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + /// + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. + /// + /// + public class DatabaseFacadeDependencies : IDatabaseFacadeDependencies + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public DatabaseFacadeDependencies( + [NotNull] IDbContextTransactionManager transactionManager, + [NotNull] IDatabaseCreator databaseCreator, + [NotNull] IExecutionStrategyFactory executionStrategyFactory, + [NotNull] IEnumerable databaseProviders, + [NotNull] IDiagnosticsLogger commandLogger, + [NotNull] IConcurrencyDetector concurrencyDetector) + { + TransactionManager = transactionManager; + DatabaseCreator = databaseCreator; + ExecutionStrategyFactory = executionStrategyFactory; + DatabaseProviders = databaseProviders; + CommandLogger = commandLogger; + ConcurrencyDetector = concurrencyDetector; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IDbContextTransactionManager TransactionManager { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IDatabaseCreator DatabaseCreator { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IExecutionStrategyFactory ExecutionStrategyFactory { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable DatabaseProviders { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IDiagnosticsLogger CommandLogger { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConcurrencyDetector ConcurrencyDetector { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DbContext Context => null; + } +} diff --git a/src/EFCore/Internal/DbContextDependencies.cs b/src/EFCore/Internal/DbContextDependencies.cs index 9ff5a8dbf7d..a0c38aad5a6 100644 --- a/src/EFCore/Internal/DbContextDependencies.cs +++ b/src/EFCore/Internal/DbContextDependencies.cs @@ -16,12 +16,14 @@ namespace Microsoft.EntityFrameworkCore.Internal /// Service dependencies parameter class for /// /// - /// This type supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This type may change or be removed in future releases. + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. /// /// - /// The service lifetime is . This means that each - /// instance will use its own instance of this service. + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. /// The implementation may depend on other services registered with any lifetime. /// The implementation does not need to be thread-safe. /// diff --git a/src/EFCore/Internal/IDatabaseFacadeDependencies.cs b/src/EFCore/Internal/IDatabaseFacadeDependencies.cs new file mode 100644 index 00000000000..9be015a2694 --- /dev/null +++ b/src/EFCore/Internal/IDatabaseFacadeDependencies.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.Internal +{ + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + /// + /// The service lifetime is . This means that each + /// instance will use its own instance of this service. + /// The implementation may depend on other services registered with any lifetime. + /// The implementation does not need to be thread-safe. + /// + /// + // This is used by Relational, but is still internal since it's only for an optimization; no provider needs access. + public interface IDatabaseFacadeDependencies + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IDbContextTransactionManager TransactionManager { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IDatabaseCreator DatabaseCreator { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IExecutionStrategyFactory ExecutionStrategyFactory { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IEnumerable DatabaseProviders { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IDiagnosticsLogger CommandLogger { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConcurrencyDetector ConcurrencyDetector { get; } + } +} diff --git a/src/EFCore/Internal/IDatabaseFacadeDependenciesAccessor.cs b/src/EFCore/Internal/IDatabaseFacadeDependenciesAccessor.cs new file mode 100644 index 00000000000..7c4d0a4a5b4 --- /dev/null +++ b/src/EFCore/Internal/IDatabaseFacadeDependenciesAccessor.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Internal +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + // This is used by Relational, but is still internal since it's only for an optimization; no provider needs access. + public interface IDatabaseFacadeDependenciesAccessor + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IDatabaseFacadeDependencies Dependencies { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + DbContext Context { get; } + } +} diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index 5550be88a63..4a5a89dcddf 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -107,6 +107,7 @@ var migrationAssembly services.GetRequiredService(), services.GetRequiredService(), services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService>(), services.GetRequiredService>(), services.GetRequiredService()))); diff --git a/test/EFCore.Relational.Specification.Tests/InterceptionTestBase.cs b/test/EFCore.Relational.Specification.Tests/InterceptionTestBase.cs index bca0f7ab266..3d4e3728913 100644 --- a/test/EFCore.Relational.Specification.Tests/InterceptionTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/InterceptionTestBase.cs @@ -60,6 +60,7 @@ public virtual async Task Intercept_query_passively(bool async, bool inj Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); } return interceptor.CommandText; @@ -91,18 +92,26 @@ public virtual async Task Intercept_scalar_passively(bool async, bool inject) if (async) { - Assert.Equal(1, Convert.ToInt32(await command.ExecuteScalarAsync(connection, null, logger))); + Assert.Equal( + 1, Convert.ToInt32( + await command.ExecuteScalarAsync( + new RelationalCommandParameterObject( + connection, null, context, logger)))); Assert.True(interceptor.AsyncCalled); } else { - Assert.Equal(1, Convert.ToInt32(command.ExecuteScalar(connection, null, logger))); + Assert.Equal( + 1, Convert.ToInt32( + command.ExecuteScalar( + new RelationalCommandParameterObject(connection, null, context, logger)))); Assert.True(interceptor.SyncCalled); } Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(sql, interceptor.CommandText); } @@ -144,6 +153,7 @@ public virtual async Task Intercept_non_query_passively(bool async, bool inject) Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(nonQuery, interceptor.CommandText); } @@ -192,6 +202,7 @@ public virtual async Task Intercept_query_to_suppress_execution(bool asy Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); } return interceptor.CommandText; @@ -246,7 +257,9 @@ public virtual async Task Intercept_scalar_to_suppress_execution(bool async, boo { Assert.Equal( SuppressingScalarCommandInterceptor.InterceptedResult, - await command.ExecuteScalarAsync(connection, null, logger)); + await command.ExecuteScalarAsync( + new RelationalCommandParameterObject( + connection, null, context, logger))); Assert.True(interceptor.AsyncCalled); } @@ -254,7 +267,9 @@ public virtual async Task Intercept_scalar_to_suppress_execution(bool async, boo { Assert.Equal( SuppressingScalarCommandInterceptor.InterceptedResult, - command.ExecuteScalar(connection, null, logger)); + command.ExecuteScalar( + new RelationalCommandParameterObject( + connection, null, context, logger))); Assert.True(interceptor.SyncCalled); } @@ -262,6 +277,7 @@ public virtual async Task Intercept_scalar_to_suppress_execution(bool async, boo Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(sql, interceptor.CommandText); } @@ -327,6 +343,7 @@ public virtual async Task Intercept_non_query_to_suppress_execution(bool async, Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(nonQuery, interceptor.CommandText); } @@ -394,6 +411,7 @@ public virtual async Task Intercept_query_to_mutate_command(bool async, Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); } return interceptor.CommandText; @@ -449,18 +467,24 @@ public virtual async Task Intercept_scalar_to_mutate_command(bool async, bool in if (async) { - Assert.Equal(2, Convert.ToInt32(await command.ExecuteScalarAsync(connection, null, logger))); + Assert.Equal(2, Convert.ToInt32(await command.ExecuteScalarAsync( + new RelationalCommandParameterObject(connection, null, context, logger)))); Assert.True(interceptor.AsyncCalled); } else { - Assert.Equal(2, Convert.ToInt32(command.ExecuteScalar(connection, null, logger))); + Assert.Equal( + 2, Convert.ToInt32( + command.ExecuteScalar( + new RelationalCommandParameterObject( + connection, null, context, logger)))); Assert.True(interceptor.SyncCalled); } Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(MutatingScalarCommandInterceptor.MutatedSql, interceptor.CommandText); } @@ -525,6 +549,7 @@ public virtual async Task Intercept_non_query_to_mutate_command(bool async, bool Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(MutatingNonQueryCommandInterceptor.MutatedSql, interceptor.CommandText); } @@ -594,6 +619,7 @@ public virtual async Task Intercept_query_to_replace_execution(bool asyn Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); } return interceptor.CommandText; @@ -656,18 +682,23 @@ public virtual async Task Intercept_scalar_to_replace_execution(bool async, bool if (async) { - Assert.Equal(2, Convert.ToInt32(await command.ExecuteScalarAsync(connection, null, logger))); + Assert.Equal(2, Convert.ToInt32(await command.ExecuteScalarAsync( + new RelationalCommandParameterObject(connection, null, context, logger)))); Assert.True(interceptor.AsyncCalled); } else { - Assert.Equal(2, Convert.ToInt32(command.ExecuteScalar(connection, null, logger))); + Assert.Equal( + 2, Convert.ToInt32( + command.ExecuteScalar( + new RelationalCommandParameterObject(connection, null, context, logger)))); Assert.True(interceptor.SyncCalled); } Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(sql, interceptor.CommandText); } @@ -740,6 +771,7 @@ public virtual async Task Intercept_non_query_to_replace_execution(bool async, b Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(nonQuery, interceptor.CommandText); } @@ -824,6 +856,7 @@ public virtual async Task Intercept_query_to_replace_result(bool async, Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); } return interceptor.CommandText; @@ -931,7 +964,8 @@ public virtual async Task Intercept_scalar_to_replace_result(bool async, bool in { Assert.Equal( ResultReplacingScalarCommandInterceptor.InterceptedResult, - await command.ExecuteScalarAsync(connection, null, logger)); + await command.ExecuteScalarAsync( + new RelationalCommandParameterObject(connection, null, context, logger))); Assert.True(interceptor.AsyncCalled); } @@ -939,7 +973,8 @@ public virtual async Task Intercept_scalar_to_replace_result(bool async, bool in { Assert.Equal( ResultReplacingScalarCommandInterceptor.InterceptedResult, - command.ExecuteScalar(connection, null, logger)); + command.ExecuteScalar( + new RelationalCommandParameterObject(connection, null, context, logger))); Assert.True(interceptor.SyncCalled); } @@ -947,6 +982,7 @@ public virtual async Task Intercept_scalar_to_replace_result(bool async, bool in Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(sql, interceptor.CommandText); } @@ -1011,6 +1047,7 @@ public virtual async Task Intercept_non_query_to_replaceresult(bool async, bool Assert.NotEqual(interceptor.AsyncCalled, interceptor.SyncCalled); Assert.True(interceptor.ExecutingCalled); Assert.True(interceptor.ExecutedCalled); + Assert.Same(context, interceptor.Context); AssertSql(nonQuery, interceptor.CommandText); } @@ -1077,8 +1114,10 @@ public virtual async Task Intercept_scalar_to_throw(bool async, bool inject) var logger = context.GetService>(); var exception = async - ? await Assert.ThrowsAsync(() => command.ExecuteScalarAsync(connection, null, logger)) - : Assert.Throws(() => command.ExecuteScalar(connection, null, logger)); + ? await Assert.ThrowsAsync(() => command.ExecuteScalarAsync( + new RelationalCommandParameterObject(connection, null, context, logger))) + : Assert.Throws(() => command.ExecuteScalar( + new RelationalCommandParameterObject(connection, null, context, logger))); Assert.Equal("Bang!", exception.Message); } @@ -1205,6 +1244,8 @@ private static async Task TestCompoisteQueryInterceptors( Assert.True(interceptor2.ExecutingCalled); Assert.True(interceptor1.ExecutedCalled); Assert.True(interceptor2.ExecutedCalled); + Assert.Same(context, interceptor1.Context); + Assert.Same(context, interceptor2.Context); } [ConditionalTheory] @@ -1229,8 +1270,10 @@ private static async Task TestCompositeScalarInterceptors(UniverseContext contex Assert.Equal( ResultReplacingScalarCommandInterceptor.InterceptedResult, async - ? await command.ExecuteScalarAsync(connection, null, logger) - : command.ExecuteScalar(connection, null, logger)); + ? await command.ExecuteScalarAsync( + new RelationalCommandParameterObject(connection, null, context, logger)) + : command.ExecuteScalar( + new RelationalCommandParameterObject(connection, null, context, logger))); } [ConditionalTheory] @@ -1436,6 +1479,7 @@ protected CommandInterceptorBase(DbCommandMethod commandMethod) _commandMethod = commandMethod; } + public DbContext Context { get; set; } public string CommandText { get; set; } public Guid CommandId { get; set; } public Guid ConnectionId { get; set; } @@ -1596,10 +1640,12 @@ public virtual Task NonQueryExecutedAsync( protected virtual void AssertExecuting(DbCommand command, CommandEventData eventData) { + Assert.NotNull(eventData.Context); Assert.NotEqual(default, eventData.CommandId); Assert.NotEqual(default, eventData.ConnectionId); Assert.Equal(_commandMethod, eventData.ExecuteMethod); + Context = eventData.Context; CommandText = command.CommandText; CommandId = eventData.CommandId; ConnectionId = eventData.ConnectionId; @@ -1608,6 +1654,7 @@ protected virtual void AssertExecuting(DbCommand command, CommandEventData event protected virtual void AssertExecuted(DbCommand command, CommandExecutedEventData eventData) { + Assert.Same(Context, eventData.Context); Assert.Equal(CommandText, command.CommandText); Assert.Equal(CommandId, eventData.CommandId); Assert.Equal(ConnectionId, eventData.ConnectionId); diff --git a/test/EFCore.Relational.Specification.Tests/TestUtilities/RelationalDatabaseCleaner.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/RelationalDatabaseCleaner.cs index b5e743cd733..54c44f58a77 100644 --- a/test/EFCore.Relational.Specification.Tests/TestUtilities/RelationalDatabaseCleaner.cs +++ b/test/EFCore.Relational.Specification.Tests/TestUtilities/RelationalDatabaseCleaner.cs @@ -40,7 +40,6 @@ public virtual void Clean(DatabaseFacade facade) var connection = facade.GetService(); var sqlBuilder = facade.GetService(); var loggerFactory = facade.GetService(); - var commandLogger = facade.GetService>(); if (!creator.Exists()) { @@ -84,7 +83,9 @@ public virtual void Clean(DatabaseFacade facade) var customSql = BuildCustomSql(databaseModel); if (!string.IsNullOrWhiteSpace(customSql)) { - sqlBuilder.Build(customSql).ExecuteNonQuery(connection, null, null); + sqlBuilder.Build(customSql).ExecuteNonQuery( + new RelationalCommandParameterObject( + connection,null, null,null)); } if (operations.Count > 0) @@ -96,7 +97,8 @@ public virtual void Clean(DatabaseFacade facade) customSql = BuildCustomEndingSql(databaseModel); if (!string.IsNullOrWhiteSpace(customSql)) { - sqlBuilder.Build(customSql).ExecuteNonQuery(connection, null, null); + sqlBuilder.Build(customSql).ExecuteNonQuery( + new RelationalCommandParameterObject(connection, null, null, null)); } } finally diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs index 8d7526fcb18..cdbe606eb35 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationCommandExecutorTest.cs @@ -25,8 +25,8 @@ public async Task Executes_migtration_commands_in_same_transaction(bool async) var commandList = new List { - new MigrationCommand(CreateRelationalCommand(), logger), - new MigrationCommand(CreateRelationalCommand(), logger) + new MigrationCommand(CreateRelationalCommand(), null, logger), + new MigrationCommand(CreateRelationalCommand(), null, logger) }; var migrationCommandExecutor = new MigrationCommandExecutor(); @@ -67,8 +67,8 @@ public async Task Executes_migration_commands_with_transaction_suppressed_outsid var commandList = new List { - new MigrationCommand(CreateRelationalCommand(), logger, transactionSuppressed: true), - new MigrationCommand(CreateRelationalCommand(), logger, transactionSuppressed: true) + new MigrationCommand(CreateRelationalCommand(), null, logger, transactionSuppressed: true), + new MigrationCommand(CreateRelationalCommand(), null, logger, transactionSuppressed: true) }; var migrationCommandExecutor = new MigrationCommandExecutor(); @@ -103,8 +103,8 @@ public async Task Ends_transaction_when_transaction_is_suppressed(bool async) var commandList = new List { - new MigrationCommand(CreateRelationalCommand(), logger), - new MigrationCommand(CreateRelationalCommand(), logger, transactionSuppressed: true) + new MigrationCommand(CreateRelationalCommand(), null, logger), + new MigrationCommand(CreateRelationalCommand(), null, logger, transactionSuppressed: true) }; var migrationCommandExecutor = new MigrationCommandExecutor(); @@ -144,8 +144,8 @@ public async Task Begins_new_transaction_when_transaction_nolonger_suppressed(bo var commandList = new List { - new MigrationCommand(CreateRelationalCommand(), logger, transactionSuppressed: true), - new MigrationCommand(CreateRelationalCommand(), logger) + new MigrationCommand(CreateRelationalCommand(), null, logger, transactionSuppressed: true), + new MigrationCommand(CreateRelationalCommand(), null, logger) }; var migrationCommandExecutor = new MigrationCommandExecutor(); @@ -185,9 +185,9 @@ public async Task Executes_commands_in_order_regardless_of_transaction_suppressi var commandList = new List { - new MigrationCommand(CreateRelationalCommand(commandText: "First"), logger), - new MigrationCommand(CreateRelationalCommand(commandText: "Second"), logger, transactionSuppressed: true), - new MigrationCommand(CreateRelationalCommand(commandText: "Third"), logger) + new MigrationCommand(CreateRelationalCommand(commandText: "First"), null, logger), + new MigrationCommand(CreateRelationalCommand(commandText: "Second"), null, logger, transactionSuppressed: true), + new MigrationCommand(CreateRelationalCommand(commandText: "Third"), null, logger) }; var migrationCommandExecutor = new MigrationCommandExecutor(); @@ -260,7 +260,7 @@ public async Task Disposes_transaction_on_exception(bool async) var commandList = new List { - new MigrationCommand(CreateRelationalCommand(), logger) + new MigrationCommand(CreateRelationalCommand(), null, logger) }; var migrationCommandExecutor = new MigrationCommandExecutor(); diff --git a/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs b/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs index 8f658a5e3c6..87ff10c91d8 100644 --- a/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/MigrationCommandListBuilderTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.TestUtilities.FakeProvider; @@ -136,7 +137,12 @@ private MigrationCommandListBuilder CreateBuilder() typeMappingSource)), generationHelper, typeMappingSource, + new CurrentDbContext(new FakeDbContext()), logger)); } + + private class FakeDbContext : DbContext + { + } } } diff --git a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs index c42d84028c8..baf43c1dfd1 100644 --- a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs +++ b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs @@ -73,7 +73,8 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() { typeof(IProperty), () => property }, { typeof(TypeInfo), () => typeof(object).GetTypeInfo() }, { typeof(Type), () => typeof(object) }, - { typeof(ValueConverter), () => new BoolToZeroOneConverter() } + { typeof(ValueConverter), () => new BoolToZeroOneConverter() }, + { typeof(DbContext), () => new FakeDbContext() } }; TestEventLogging( @@ -108,6 +109,10 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() }); } + private class FakeDbContext : DbContext + { + } + private class FakeMigration : Migration { protected override void Up(MigrationBuilder migrationBuilder) => throw new NotImplementedException(); diff --git a/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs index 1119e4d6cca..8967c730c1d 100644 --- a/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs +++ b/test/EFCore.Relational.Tests/Storage/RelationalCommandTest.cs @@ -43,7 +43,8 @@ public void Configures_DbCommand() var fakeConnection = CreateConnection(); var relationalCommand = CreateRelationalCommand(commandText: "CommandText"); - relationalCommand.ExecuteNonQuery(fakeConnection, null, null); + relationalCommand.ExecuteNonQuery( + new RelationalCommandParameterObject(fakeConnection, null, null, null)); Assert.Equal(1, fakeConnection.DbConnections.Count); Assert.Equal(1, fakeConnection.DbConnections[0].DbCommands.Count); @@ -64,7 +65,8 @@ public void Configures_DbCommand_with_transaction() var relationalCommand = CreateRelationalCommand(); - relationalCommand.ExecuteNonQuery(fakeConnection, null, null); + relationalCommand.ExecuteNonQuery( + new RelationalCommandParameterObject(fakeConnection, null, null, null)); Assert.Equal(1, fakeConnection.DbConnections.Count); Assert.Equal(1, fakeConnection.DbConnections[0].DbCommands.Count); @@ -85,7 +87,8 @@ public void Configures_DbCommand_with_timeout() var relationalCommand = CreateRelationalCommand(); - relationalCommand.ExecuteNonQuery(fakeConnection, null, null); + relationalCommand.ExecuteNonQuery( + new RelationalCommandParameterObject(fakeConnection, null, null, null)); Assert.Equal(1, fakeConnection.DbConnections.Count); Assert.Equal(1, fakeConnection.DbConnections[0].DbCommands.Count); @@ -118,7 +121,8 @@ public void Can_ExecuteNonQuery() var relationalCommand = CreateRelationalCommand(); var result = relationalCommand.ExecuteNonQuery( - new FakeRelationalConnection(options), null, null); + new RelationalCommandParameterObject( + new FakeRelationalConnection(options), null, null, null)); Assert.Equal(1, result); @@ -157,7 +161,8 @@ public virtual async Task Can_ExecuteNonQueryAsync() var relationalCommand = CreateRelationalCommand(); var result = await relationalCommand.ExecuteNonQueryAsync( - new FakeRelationalConnection(options), null, null); + new RelationalCommandParameterObject( + new FakeRelationalConnection(options), null, null, null)); Assert.Equal(1, result); @@ -196,7 +201,8 @@ public void Can_ExecuteScalar() var relationalCommand = CreateRelationalCommand(); var result = (string)relationalCommand.ExecuteScalar( - new FakeRelationalConnection(options), null, null); + new RelationalCommandParameterObject( + new FakeRelationalConnection(options), null, null, null)); Assert.Equal("ExecuteScalar Result", result); @@ -235,7 +241,8 @@ public async Task Can_ExecuteScalarAsync() var relationalCommand = CreateRelationalCommand(); var result = (string)await relationalCommand.ExecuteScalarAsync( - new FakeRelationalConnection(options), null, null); + new RelationalCommandParameterObject( + new FakeRelationalConnection(options), null, null, null)); Assert.Equal("ExecuteScalar Result", result); @@ -276,7 +283,8 @@ public void Can_ExecuteReader() var relationalCommand = CreateRelationalCommand(); var result = relationalCommand.ExecuteReader( - new FakeRelationalConnection(options), null, null); + new RelationalCommandParameterObject( + new FakeRelationalConnection(options), null, null, null)); Assert.Same(dbDataReader, result.DbDataReader); Assert.Equal(0, fakeDbConnection.CloseCount); @@ -324,7 +332,8 @@ public async Task Can_ExecuteReaderAsync() var relationalCommand = CreateRelationalCommand(); var result = await relationalCommand.ExecuteReaderAsync( - new FakeRelationalConnection(options), null, null); + new RelationalCommandParameterObject( + new FakeRelationalConnection(options), null, null, null)); Assert.Same(dbDataReader, result.DbDataReader); Assert.Equal(0, fakeDbConnection.CloseCount); @@ -353,42 +362,48 @@ public static TheoryData CommandActions { new CommandAction( (connection, command, parameterValues, logger) - => command.ExecuteNonQuery(connection, parameterValues, logger)), + => command.ExecuteNonQuery( + new RelationalCommandParameterObject(connection, parameterValues, null, logger))), DbCommandMethod.ExecuteNonQuery, false }, { new CommandAction( (connection, command, parameterValues, logger) - => command.ExecuteScalar(connection, parameterValues, logger)), + => command.ExecuteScalar( + new RelationalCommandParameterObject(connection, parameterValues, null, logger))), DbCommandMethod.ExecuteScalar, false }, { new CommandAction( (connection, command, parameterValues, logger) - => command.ExecuteReader(connection, parameterValues, logger)), + => command.ExecuteReader( + new RelationalCommandParameterObject(connection, parameterValues, null, logger))), DbCommandMethod.ExecuteReader, false }, { new CommandFunc( (connection, command, parameterValues, logger) - => command.ExecuteNonQueryAsync(connection, parameterValues, logger)), + => command.ExecuteNonQueryAsync( + new RelationalCommandParameterObject(connection, parameterValues, null, logger))), DbCommandMethod.ExecuteNonQuery, true }, { new CommandFunc( (connection, command, parameterValues, logger) - => command.ExecuteScalarAsync(connection, parameterValues, logger)), + => command.ExecuteScalarAsync( + new RelationalCommandParameterObject(connection, parameterValues, null, logger))), DbCommandMethod.ExecuteScalar, true }, { new CommandFunc( (connection, command, parameterValues, logger) - => command.ExecuteReaderAsync(connection, parameterValues, logger)), + => command.ExecuteReaderAsync( + new RelationalCommandParameterObject(connection, parameterValues, null, logger))), DbCommandMethod.ExecuteReader, true } diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs index e1087be7253..17ccf714173 100644 --- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs +++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs @@ -588,6 +588,7 @@ private static ModificationCommandBatchFactoryDependencies CreateDependencies( new RelationalValueBufferFactoryDependencies( typeMappingSource, new CoreSingletonOptions())), + new CurrentDbContext(new FakeDbContext()), logger); } @@ -613,6 +614,10 @@ public RawSqlCommand CreateStoreCommandBase() => CreateStoreCommand(); } + private class FakeDbContext : DbContext + { + } + private const string ConnectionString = "Fake Connection String"; private static FakeRelationalConnection CreateConnection(DbDataReader dbDataReader) diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs index c86c4de143e..2a95080a73b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestRelationalCommandBuilderFactory.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -81,7 +80,7 @@ public IRelationalCommandBuilder DecrementIndent() return this; } - + public int CommandTextLength => Instance.Length; } @@ -101,14 +100,12 @@ public TestRelationalCommand( public IReadOnlyList Parameters => _realRelationalCommand.Parameters; - public int ExecuteNonQuery( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public int ExecuteNonQuery(RelationalCommandParameterObject parameterObject) { + var connection = parameterObject.Connection; var errorNumber = PreExecution(connection); - var result = _realRelationalCommand.ExecuteNonQuery(connection, parameterValues, logger); + var result = _realRelationalCommand.ExecuteNonQuery(parameterObject); if (errorNumber.HasValue) { connection.DbConnection.Close(); @@ -119,13 +116,13 @@ public int ExecuteNonQuery( } public Task ExecuteNonQueryAsync( - IRelationalConnection connection, IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = new CancellationToken()) { + var connection = parameterObject.Connection; var errorNumber = PreExecution(connection); - var result = _realRelationalCommand.ExecuteNonQueryAsync(connection, parameterValues, logger, cancellationToken); + var result = _realRelationalCommand.ExecuteNonQueryAsync(parameterObject, cancellationToken); if (errorNumber.HasValue) { connection.DbConnection.Close(); @@ -135,14 +132,12 @@ public Task ExecuteNonQueryAsync( return result; } - public object ExecuteScalar( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public object ExecuteScalar(RelationalCommandParameterObject parameterObject) { + var connection = parameterObject.Connection; var errorNumber = PreExecution(connection); - var result = _realRelationalCommand.ExecuteScalar(connection, parameterValues, logger); + var result = _realRelationalCommand.ExecuteScalar(parameterObject); if (errorNumber.HasValue) { connection.DbConnection.Close(); @@ -153,13 +148,13 @@ public object ExecuteScalar( } public async Task ExecuteScalarAsync( - IRelationalConnection connection, IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = new CancellationToken()) { + var connection = parameterObject.Connection; var errorNumber = PreExecution(connection); - var result = await _realRelationalCommand.ExecuteScalarAsync(connection, parameterValues, logger, cancellationToken); + var result = await _realRelationalCommand.ExecuteScalarAsync(parameterObject, cancellationToken); if (errorNumber.HasValue) { connection.DbConnection.Close(); @@ -169,14 +164,12 @@ public async Task ExecuteScalarAsync( return result; } - public RelationalDataReader ExecuteReader( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public RelationalDataReader ExecuteReader(RelationalCommandParameterObject parameterObject) { + var connection = parameterObject.Connection; var errorNumber = PreExecution(connection); - var result = _realRelationalCommand.ExecuteReader(connection, parameterValues, logger); + var result = _realRelationalCommand.ExecuteReader(parameterObject); if (errorNumber.HasValue) { connection.DbConnection.Close(); @@ -188,13 +181,13 @@ public RelationalDataReader ExecuteReader( } public async Task ExecuteReaderAsync( - IRelationalConnection connection, IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = new CancellationToken()) { + var connection = parameterObject.Connection; var errorNumber = PreExecution(connection); - var result = await _realRelationalCommand.ExecuteReaderAsync(connection, parameterValues, logger, cancellationToken); + var result = await _realRelationalCommand.ExecuteReaderAsync(parameterObject, cancellationToken); if (errorNumber.HasValue) { connection.DbConnection.Close(); diff --git a/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs b/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs index 5eca5acdd3d..e58d196cdef 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; @@ -261,47 +260,35 @@ private class FakeRelationalCommand : IRelationalCommand public IReadOnlyDictionary ParameterValues => throw new NotImplementedException(); - public int ExecuteNonQuery( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public int ExecuteNonQuery(RelationalCommandParameterObject parameterObject) { return 0; } public Task ExecuteNonQueryAsync( - IRelationalConnection connection, IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) => Task.FromResult(0); - public RelationalDataReader ExecuteReader( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public RelationalDataReader ExecuteReader(RelationalCommandParameterObject parameterObject) { throw new NotImplementedException(); } public Task ExecuteReaderAsync( - IRelationalConnection connection, IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public object ExecuteScalar( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public object ExecuteScalar(RelationalCommandParameterObject parameterObject) { throw new NotImplementedException(); } public Task ExecuteScalarAsync( - IRelationalConnection connection, IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/test/EFCore.SqlServer.Tests/SqlServerSequenceValueGeneratorTest.cs b/test/EFCore.SqlServer.Tests/SqlServerSequenceValueGeneratorTest.cs index 0afcdf9c419..d9fffaf3592 100644 --- a/test/EFCore.SqlServer.Tests/SqlServerSequenceValueGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/SqlServerSequenceValueGeneratorTest.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; @@ -236,48 +235,33 @@ public FakeRelationalCommand(FakeRawSqlCommandBuilder commandBuilder) public IReadOnlyDictionary ParameterValues => throw new NotImplementedException(); - public int ExecuteNonQuery( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public int ExecuteNonQuery(RelationalCommandParameterObject parameterObject) { throw new NotImplementedException(); } public Task ExecuteNonQueryAsync( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } - public object ExecuteScalar( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public object ExecuteScalar(RelationalCommandParameterObject parameterObject) => Interlocked.Add(ref _commandBuilder._current, _commandBuilder._blockSize); public Task ExecuteScalarAsync( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) => Task.FromResult(Interlocked.Add(ref _commandBuilder._current, _commandBuilder._blockSize)); - public RelationalDataReader ExecuteReader( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public RelationalDataReader ExecuteReader(RelationalCommandParameterObject parameterObject) { throw new NotImplementedException(); } public Task ExecuteReaderAsync( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger, + RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs index 6c17ef9a89b..01ce0dca619 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchFactoryTest.cs @@ -40,6 +40,7 @@ public void Uses_MaxBatchSize_specified_in_SqlServerOptionsExtension() new TypedRelationalValueBufferFactoryFactory( new RelationalValueBufferFactoryDependencies( typeMapper, new CoreSingletonOptions())), + new CurrentDbContext(new FakeDbContext()), logger), optionsBuilder.Options); @@ -76,6 +77,7 @@ public void MaxBatchSize_is_optional() new TypedRelationalValueBufferFactoryFactory( new RelationalValueBufferFactoryDependencies( typeMapper, new CoreSingletonOptions())), + new CurrentDbContext(new FakeDbContext()), logger), optionsBuilder.Options); @@ -84,5 +86,9 @@ public void MaxBatchSize_is_optional() Assert.True(batch.AddCommand(new ModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null))); Assert.True(batch.AddCommand(new ModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null))); } + + private class FakeDbContext : DbContext + { + } } } diff --git a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs index b58e9d26f69..bd051e4b879 100644 --- a/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs +++ b/test/EFCore.SqlServer.Tests/Update/SqlServerModificationCommandBatchTest.cs @@ -37,6 +37,7 @@ public void AddCommand_returns_false_when_max_batch_size_is_reached() new TypedRelationalValueBufferFactoryFactory( new RelationalValueBufferFactoryDependencies( typeMapper, new CoreSingletonOptions())), + new CurrentDbContext(new FakeDbContext()), logger), 1); @@ -47,5 +48,9 @@ public void AddCommand_returns_false_when_max_batch_size_is_reached() batch.AddCommand( new ModificationCommand("T1", null, new ParameterNameGenerator().GenerateNext, false, null))); } + + private class FakeDbContext : DbContext + { + } } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs index 8ac0adc45c2..54a1f497384 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/BadDataSqliteTest.cs @@ -184,14 +184,14 @@ public BadDataRelationalCommand( _values = values; } - public override RelationalDataReader ExecuteReader( - IRelationalConnection connection, - IReadOnlyDictionary parameterValues, - IDiagnosticsLogger logger) + public override RelationalDataReader ExecuteReader(RelationalCommandParameterObject parameterObject) { - var command = connection.DbConnection.CreateCommand(); + var command = parameterObject.Connection.DbConnection.CreateCommand(); command.CommandText = CommandText; - return new BadDataRelationalDataReader(command, _values, logger); + return new BadDataRelationalDataReader( + command, + _values, + parameterObject.Logger); } private class BadDataRelationalDataReader : RelationalDataReader