diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Infrastructure/ServiceCollectionRelationalProviderInfrastructure.cs b/src/Microsoft.EntityFrameworkCore.Relational/Infrastructure/ServiceCollectionRelationalProviderInfrastructure.cs index 9598ec84d3a..4141b56b165 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Infrastructure/ServiceCollectionRelationalProviderInfrastructure.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Infrastructure/ServiceCollectionRelationalProviderInfrastructure.cs @@ -87,6 +87,12 @@ public static void TryAddDefaultRelationalServices([NotNull] IServiceCollection .AddScoped() .AddScoped()); + // Add service dependencies parameter classes. + // These are not try-added and are added as concrete types because these registrations should + // not be changed by provider or application code. + serviceCollection + .AddScoped(); + ServiceCollectionProviderInfrastructure.TryAddDefaultEntityFrameworkServices(serviceCollection); } } diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Microsoft.EntityFrameworkCore.Relational.csproj b/src/Microsoft.EntityFrameworkCore.Relational/Microsoft.EntityFrameworkCore.Relational.csproj index 338ad7f6b13..1ac268224de 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Microsoft.EntityFrameworkCore.Relational.csproj +++ b/src/Microsoft.EntityFrameworkCore.Relational/Microsoft.EntityFrameworkCore.Relational.csproj @@ -339,6 +339,7 @@ + diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Storage/RelationalConnection.cs b/src/Microsoft.EntityFrameworkCore.Relational/Storage/RelationalConnection.cs index 26c64099364..f606b6b245e 100644 --- a/src/Microsoft.EntityFrameworkCore.Relational/Storage/RelationalConnection.cs +++ b/src/Microsoft.EntityFrameworkCore.Relational/Storage/RelationalConnection.cs @@ -37,21 +37,18 @@ public abstract class RelationalConnection : IRelationalConnection private int _openedCount; private bool _openedInternally; private int? _commandTimeout; - private readonly ILogger _logger; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The options for the context that this connection will be used with. - /// The logger to write to. - protected RelationalConnection([NotNull] IDbContextOptions options, [NotNull] ILogger logger) + /// Parameter object containing dependencies for this service. + protected RelationalConnection([NotNull] RelationalConnectionDependencies dependencies) { - Check.NotNull(options, nameof(options)); - Check.NotNull(logger, nameof(logger)); + Check.NotNull(dependencies, nameof(dependencies)); - _logger = logger; + Dependencies = dependencies; - var relationalOptions = RelationalOptionsExtension.Extract(options); + var relationalOptions = RelationalOptionsExtension.Extract(dependencies.ContextOptions); _commandTimeout = relationalOptions.CommandTimeout; @@ -77,6 +74,11 @@ protected RelationalConnection([NotNull] IDbContextOptions options, [NotNull] IL } } + /// + /// Parameter object containing service dependencies. + /// + protected virtual RelationalConnectionDependencies Dependencies { get; } + /// /// Creates a to the database. /// @@ -86,7 +88,7 @@ protected RelationalConnection([NotNull] IDbContextOptions options, [NotNull] IL /// /// Gets the logger to write to. /// - protected virtual ILogger Logger => _logger; + protected virtual ILogger Logger => Dependencies.Logger; /// /// Gets the connection string for the database. @@ -112,7 +114,7 @@ public virtual int? CommandTimeout set { if (value.HasValue - && (value < 0)) + && value < 0) { throw new ArgumentException(RelationalStrings.InvalidCommandTimeout); } @@ -182,9 +184,7 @@ public virtual async Task BeginTransactionAsync( private IDbContextTransaction BeginTransactionWithNoPreconditions(IsolationLevel isolationLevel) { - Check.NotNull(_logger, nameof(_logger)); - - _logger.LogDebug( + Logger.LogDebug( RelationalEventId.BeginningTransaction, isolationLevel, il => RelationalStrings.RelationalLoggerBeginningTransaction(il.ToString("G"))); @@ -193,7 +193,7 @@ private IDbContextTransaction BeginTransactionWithNoPreconditions(IsolationLevel = new RelationalTransaction( this, DbConnection.BeginTransaction(isolationLevel), - _logger, + Logger, transactionOwned: true); return CurrentTransaction; @@ -221,7 +221,7 @@ public virtual IDbContextTransaction UseTransaction(DbTransaction transaction) Open(); - CurrentTransaction = new RelationalTransaction(this, transaction, _logger, transactionOwned: false); + CurrentTransaction = new RelationalTransaction(this, transaction, Logger, transactionOwned: false); } return CurrentTransaction; @@ -267,7 +267,7 @@ public virtual void Open() if (_connection.Value.State != ConnectionState.Open) { - _logger.LogDebug( + Logger.LogDebug( RelationalEventId.OpeningConnection, new { @@ -311,7 +311,7 @@ public virtual void Open() if (_connection.Value.State != ConnectionState.Open) { - _logger.LogDebug( + Logger.LogDebug( RelationalEventId.OpeningConnection, new { @@ -360,7 +360,7 @@ public virtual void Close() { if (_connection.Value.State != ConnectionState.Closed) { - _logger.LogDebug( + Logger.LogDebug( RelationalEventId.ClosingConnection, new { @@ -388,7 +388,7 @@ public virtual void Close() public virtual IValueBufferCursor ActiveCursor { get; set; } void IResettableService.Reset() => Dispose(); - + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/src/Microsoft.EntityFrameworkCore.Relational/Storage/RelationalConnectionDependencies.cs b/src/Microsoft.EntityFrameworkCore.Relational/Storage/RelationalConnectionDependencies.cs new file mode 100644 index 00000000000..5ce6f8ac627 --- /dev/null +++ b/src/Microsoft.EntityFrameworkCore.Relational/Storage/RelationalConnectionDependencies.cs @@ -0,0 +1,71 @@ +// 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 JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Utilities; +using Microsoft.Extensions.Logging; + +namespace Microsoft.EntityFrameworkCore.Storage +{ + /// + /// + /// Service dependencies parameter class for + /// + /// + /// This type is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + public sealed class RelationalConnectionDependencies + { + /// + /// + /// Creates the service dependencies parameter object for a . + /// + /// + /// Do not call this constructor directly from provider or application code as it may change + /// as new dependencies are added. Use the method instead. + /// + /// + /// The options for the current context instance. + /// The logger to write to. + public RelationalConnectionDependencies( + [NotNull] IDbContextOptions contextOptions, + [NotNull] ILogger logger) + { + Check.NotNull(contextOptions, nameof(contextOptions)); + Check.NotNull(logger, nameof(logger)); + + ContextOptions = contextOptions; + Logger = logger; + } + + /// + /// The options for the current context instance. + /// + public IDbContextOptions ContextOptions { get; } + + /// + /// The logger to write to. + /// + public ILogger Logger { get; } + + /// + /// Clones this dependency parameter object, optionally replacing existing dependencies. + /// + /// + /// A replacement for the current dependency of this type, or null to continue to use the current dependency. + /// + /// + /// A replacement for the current dependency of this type, or null to continue to use the current dependency. + /// + /// + public RelationalConnectionDependencies Clone( + [CanBeNull] IDbContextOptions contextOptions = null, + [CanBeNull] ILogger logger = null) + => new RelationalConnectionDependencies( + contextOptions ?? ContextOptions, + logger ?? Logger); + } +} diff --git a/src/Microsoft.EntityFrameworkCore.SqlServer/Storage/Internal/SqlServerConnection.cs b/src/Microsoft.EntityFrameworkCore.SqlServer/Storage/Internal/SqlServerConnection.cs index f9812ca4b39..e901486bd42 100644 --- a/src/Microsoft.EntityFrameworkCore.SqlServer/Storage/Internal/SqlServerConnection.cs +++ b/src/Microsoft.EntityFrameworkCore.SqlServer/Storage/Internal/SqlServerConnection.cs @@ -4,8 +4,6 @@ using System.Data.Common; using System.Data.SqlClient; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Storage.Internal { @@ -24,17 +22,8 @@ public class SqlServerConnection : RelationalConnection, ISqlServerConnection /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public SqlServerConnection( - [NotNull] IDbContextOptions options, - // ReSharper disable once SuggestBaseTypeForParameter - [NotNull] ILogger logger) - : base(options, logger) - { - } - - private SqlServerConnection( - [NotNull] IDbContextOptions options, [NotNull] ILogger logger) - : base(options, logger) + public SqlServerConnection([NotNull] RelationalConnectionDependencies dependencies) + : base(dependencies) { } @@ -51,11 +40,16 @@ private SqlServerConnection( /// public virtual ISqlServerConnection CreateMasterConnection() { - var builder = new SqlConnectionStringBuilder(ConnectionString) { InitialCatalog = "master" }; - builder.Remove("AttachDBFilename"); + var connectionStringBuilder = new SqlConnectionStringBuilder(ConnectionString) { InitialCatalog = "master" }; + connectionStringBuilder.Remove("AttachDBFilename"); + + var contextOptions = new DbContextOptionsBuilder() + .UseSqlServer( + connectionStringBuilder.ConnectionString, + b => b.CommandTimeout(CommandTimeout ?? DefaultMasterConnectionCommandTimeout)) + .Options; - return new SqlServerConnection(new DbContextOptionsBuilder() - .UseSqlServer(builder.ConnectionString, b => b.CommandTimeout(CommandTimeout ?? DefaultMasterConnectionCommandTimeout)).Options, Logger); + return new SqlServerConnection(Dependencies.Clone(contextOptions)); } /// diff --git a/src/Microsoft.EntityFrameworkCore.Sqlite/Storage/Internal/SqliteRelationalConnection.cs b/src/Microsoft.EntityFrameworkCore.Sqlite/Storage/Internal/SqliteRelationalConnection.cs index 77af3028b6f..6f4d970bec2 100644 --- a/src/Microsoft.EntityFrameworkCore.Sqlite/Storage/Internal/SqliteRelationalConnection.cs +++ b/src/Microsoft.EntityFrameworkCore.Sqlite/Storage/Internal/SqliteRelationalConnection.cs @@ -7,10 +7,8 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Utilities; -using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.Storage.Internal { @@ -29,17 +27,15 @@ public class SqliteRelationalConnection : RelationalConnection, ISqliteRelationa /// directly from your code. This API may change or be removed in future releases. /// public SqliteRelationalConnection( - [NotNull] IRawSqlCommandBuilder rawSqlCommandBuilder, - [NotNull] IDbContextOptions options, - // ReSharper disable once SuggestBaseTypeForParameter - [NotNull] ILogger logger) - : base(options, logger) + [NotNull] RelationalConnectionDependencies dependencies, + [NotNull] IRawSqlCommandBuilder rawSqlCommandBuilder) + : base(dependencies) { Check.NotNull(rawSqlCommandBuilder, nameof(rawSqlCommandBuilder)); _rawSqlCommandBuilder = rawSqlCommandBuilder; - var optionsExtension = options.Extensions.OfType().FirstOrDefault(); + var optionsExtension = dependencies.ContextOptions.Extensions.OfType().FirstOrDefault(); if (optionsExtension != null) { _enforceForeignKeys = optionsExtension.EnforceForeignKeys; @@ -119,18 +115,14 @@ private void EnableForeignKeys() /// public virtual ISqliteRelationalConnection CreateReadOnlyConnection() { - var builder = new SqliteConnectionStringBuilder(ConnectionString) + var connectionStringBuilder = new SqliteConnectionStringBuilder(ConnectionString) { Mode = SqliteOpenMode.ReadOnly }; - var options = new DbContextOptionsBuilder(); - options.UseSqlite(builder.ToString()); + var contextOptions = new DbContextOptionsBuilder().UseSqlite(connectionStringBuilder.ToString()).Options; - return new SqliteRelationalConnection( - _rawSqlCommandBuilder, - options.Options, - (ILogger)Logger); + return new SqliteRelationalConnection(Dependencies.Clone(contextOptions), _rawSqlCommandBuilder); } } } diff --git a/test/Microsoft.EntityFrameworkCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs b/test/Microsoft.EntityFrameworkCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs index a3cc51feb23..667ecb18c1a 100644 --- a/test/Microsoft.EntityFrameworkCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs +++ b/test/Microsoft.EntityFrameworkCore.Relational.Tests/TestUtilities/FakeProvider/FakeRelationalConnection.cs @@ -14,7 +14,9 @@ public class FakeRelationalConnection : RelationalConnection private readonly List _dbConnections = new List(); public FakeRelationalConnection(IDbContextOptions options) - : base(options, new Logger(new LoggerFactory())) + : base(new RelationalConnectionDependencies( + options, + new Logger(new LoggerFactory()))) { } diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/Utilities/TestSqlServerConnection.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/Utilities/TestSqlServerConnection.cs index b613f35c0d5..d5f2271e721 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/Utilities/TestSqlServerConnection.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests/Utilities/TestSqlServerConnection.cs @@ -7,11 +7,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; -using Microsoft.Extensions.Logging; namespace Microsoft.EntityFrameworkCore.SqlServer.FunctionalTests.Utilities { @@ -19,8 +17,8 @@ public class TestSqlServerConnection : ISqlServerConnection { private readonly ISqlServerConnection _realConnection; - public TestSqlServerConnection(IDbContextOptions options, ILogger logger) - : this(new SqlServerConnection(options, logger)) + public TestSqlServerConnection(RelationalConnectionDependencies dependencies) + : this(new SqlServerConnection(dependencies)) { } diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerConnectionTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerConnectionTest.cs index 16a94d01f97..9e3887315c3 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerConnectionTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerConnectionTest.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.Extensions.Logging; using Xunit; @@ -14,7 +14,7 @@ public class SqlServerConnectionTest [Fact] public void Creates_SQL_Server_connection_string() { - using (var connection = new SqlServerConnection(CreateOptions(), new Logger(new LoggerFactory()))) + using (var connection = new SqlServerConnection(CreateDependencies())) { Assert.IsType(connection.DbConnection); } @@ -23,7 +23,7 @@ public void Creates_SQL_Server_connection_string() [Fact] public void Can_create_master_connection() { - using (var connection = new SqlServerConnection(CreateOptions(), new Logger(new LoggerFactory()))) + using (var connection = new SqlServerConnection(CreateDependencies())) { using (var master = connection.CreateMasterConnection()) { @@ -36,10 +36,13 @@ public void Can_create_master_connection() [Fact] public void Master_connection_string_contains_filename() { - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlServer(@"Server=(localdb)\MSSQLLocalDB;Database=SqlServerConnectionTest;AttachDBFilename=C:\Narf.mdf"); + var options = new DbContextOptionsBuilder() + .UseSqlServer( + @"Server=(localdb)\MSSQLLocalDB;Database=SqlServerConnectionTest;AttachDBFilename=C:\Narf.mdf", + b => b.CommandTimeout(55)) + .Options; - using (var connection = new SqlServerConnection(optionsBuilder.Options, new Logger(new LoggerFactory()))) + using (var connection = new SqlServerConnection(CreateDependencies(options))) { using (var master = connection.CreateMasterConnection()) { @@ -51,12 +54,13 @@ public void Master_connection_string_contains_filename() [Fact] public void Master_connection_string_none_default_command_timeout() { - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlServer( - @"Server=(localdb)\MSSQLLocalDB;Database=SqlServerConnectionTest", - b => b.CommandTimeout(55)); + var options = new DbContextOptionsBuilder() + .UseSqlServer( + @"Server=(localdb)\MSSQLLocalDB;Database=SqlServerConnectionTest", + b => b.CommandTimeout(55)) + .Options; - using (var connection = new SqlServerConnection(optionsBuilder.Options, new Logger(new LoggerFactory()))) + using (var connection = new SqlServerConnection(CreateDependencies(options))) { using (var master = connection.CreateMasterConnection()) { @@ -65,12 +69,14 @@ public void Master_connection_string_none_default_command_timeout() } } - public static IDbContextOptions CreateOptions() + public static RelationalConnectionDependencies CreateDependencies(DbContextOptions options = null) { - var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlServer(@"Server=(localdb)\MSSQLLocalDB;Database=SqlServerConnectionTest"); + options = options + ?? new DbContextOptionsBuilder() + .UseSqlServer(@"Server=(localdb)\MSSQLLocalDB;Database=SqlServerConnectionTest") + .Options; - return optionsBuilder.Options; + return new RelationalConnectionDependencies(options, new Logger(new LoggerFactory())); } } } diff --git a/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs b/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs index 1d7520b8249..1d47d21a351 100644 --- a/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs +++ b/test/Microsoft.EntityFrameworkCore.SqlServer.Tests/SqlServerDatabaseCreatorTest.cs @@ -156,7 +156,7 @@ private class FakeSqlServerConnection : SqlServerConnection private readonly ILoggerFactory _loggerFactory; public FakeSqlServerConnection(IDbContextOptions options, ILoggerFactory loggerFactory) - : base(options, new Logger(loggerFactory)) + : base(new RelationalConnectionDependencies(options, new Logger(loggerFactory))) { _options = options; _loggerFactory = loggerFactory;