diff --git a/src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs b/src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs index 3c83769..1fa85f5 100644 --- a/src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs +++ b/src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs @@ -18,12 +18,71 @@ namespace Microsoft.EntityFrameworkCore /// public static class JetDbContextOptionsBuilderExtensions { + #region Connection String + + /// + /// Configures the context to connect to a Microsoft Jet database. + /// + /// The builder being used to configure the context. + /// The connection string of the database to connect to. The underlying data + /// access provider (ODBC or OLE DB) will be inferred from the style of this connection string. + /// An optional action to allow additional Jet specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseJet( + [NotNull] this DbContextOptionsBuilder optionsBuilder, + [NotNull] string connectionString, + [CanBeNull] Action jetOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder) optionsBuilder, connectionString, jetOptionsAction); + + /// + /// Configures the context to connect to a Microsoft Jet database. + /// + /// The builder being used to configure the context. + /// The connection string of the database to connect to. The underlying data + /// access provider (ODBC or OLE DB) will be inferred from the style of this connection string. + /// An optional action to allow additional Jet specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseJet( + [NotNull] this DbContextOptionsBuilder optionsBuilder, + [NotNull] string connectionString, + [CanBeNull] Action jetOptionsAction = null) + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + Check.NotEmpty(connectionString, nameof(connectionString)); + + return UseJetCore(optionsBuilder, connectionString, null, JetConnection.GetDataAccessProviderType(connectionString), jetOptionsAction); + } + /// /// Configures the context to connect to a Microsoft Jet database. /// /// The builder being used to configure the context. /// The connection string of the database to connect to. - /// A `OdbcFactory` or `OleDbFactory` object to be used for all + /// An `OdbcFactory` or `OleDbFactory` object to be used for all + /// data access operations by the Jet connection. + /// An optional action to allow additional Jet specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseJet( + [NotNull] this DbContextOptionsBuilder optionsBuilder, + [NotNull] string connectionString, + [NotNull] DbProviderFactory dataAccessProviderFactory, + [CanBeNull] Action jetOptionsAction = null) + where TContext : DbContext + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + Check.NotEmpty(connectionString, nameof(connectionString)); + Check.NotNull(dataAccessProviderFactory, nameof(dataAccessProviderFactory)); + + return (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder) optionsBuilder, connectionString, dataAccessProviderFactory, jetOptionsAction); + } + + /// + /// Configures the context to connect to a Microsoft Jet database. + /// + /// The builder being used to configure the context. + /// The connection string of the database to connect to. + /// An `OdbcFactory` or `OleDbFactory` object to be used for all /// data access operations by the Jet connection. /// An optional action to allow additional Jet specific configuration. /// The options builder so that further configuration can be chained. @@ -37,22 +96,78 @@ namespace Microsoft.EntityFrameworkCore Check.NotEmpty(connectionString, nameof(connectionString)); Check.NotNull(dataAccessProviderFactory, nameof(dataAccessProviderFactory)); - return UseJetCore(optionsBuilder, connectionString, dataAccessProviderFactory, jetOptionsAction); + return UseJetCore(optionsBuilder, connectionString, dataAccessProviderFactory, null, jetOptionsAction); + } + + /// + /// Configures the context to connect to a Microsoft Jet database. + /// + /// The builder being used to configure the context. + /// The connection string of the database to connect to. + /// The type of the data access provider (`Odbc` or `OleDb`) to be used for all + /// data access operations by the Jet connection. + /// An optional action to allow additional Jet specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseJet( + [NotNull] this DbContextOptionsBuilder optionsBuilder, + [NotNull] string connectionString, + DataAccessProviderType dataAccessProviderType, + [CanBeNull] Action jetOptionsAction = null) + where TContext : DbContext + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + Check.NotEmpty(connectionString, nameof(connectionString)); + + return (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder)optionsBuilder, connectionString, dataAccessProviderType, jetOptionsAction); + } + + /// + /// Configures the context to connect to a Microsoft Jet database. + /// + /// The builder being used to configure the context. + /// The connection string of the database to connect to. + /// The type of the data access provider (`Odbc` or `OleDb`) to be used for all + /// data access operations by the Jet connection. + /// An optional action to allow additional Jet specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseJet( + [NotNull] this DbContextOptionsBuilder optionsBuilder, + [NotNull] string connectionString, + DataAccessProviderType dataAccessProviderType, + [CanBeNull] Action jetOptionsAction = null) + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + Check.NotEmpty(connectionString, nameof(connectionString)); + + return UseJetCore(optionsBuilder, connectionString, null, dataAccessProviderType, jetOptionsAction); } + internal static DbContextOptionsBuilder UseJetWithoutPredefinedDataAccessProvider( + [NotNull] this DbContextOptionsBuilder optionsBuilder, + [NotNull] string connectionString, + [CanBeNull] Action jetOptionsAction = null) + => UseJetCore(optionsBuilder, connectionString, null, null, jetOptionsAction); + private static DbContextOptionsBuilder UseJetCore( [NotNull] DbContextOptionsBuilder optionsBuilder, [NotNull] string connectionString, [CanBeNull] DbProviderFactory dataAccessProviderFactory, + [CanBeNull] DataAccessProviderType? dataAccessProviderType, Action jetOptionsAction) { Check.NotNull(optionsBuilder, nameof(optionsBuilder)); Check.NotEmpty(connectionString, nameof(connectionString)); + if (dataAccessProviderFactory == null && dataAccessProviderType == null) + { + throw new ArgumentException($"One of the parameters {nameof(dataAccessProviderFactory)} and {nameof(dataAccessProviderType)} must not be null."); + } + var extension = (JetOptionsExtension) GetOrCreateExtension(optionsBuilder) .WithConnectionString(connectionString); - extension = extension.WithDataAccessProviderFactory(dataAccessProviderFactory); + extension = extension.WithDataAccessProviderFactory( + dataAccessProviderFactory ?? JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType.Value)); ((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(extension); @@ -63,6 +178,31 @@ namespace Microsoft.EntityFrameworkCore return optionsBuilder; } + #endregion + + #region Connection + + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 + /// + /// Configures the context to connect to a Microsoft Jet database. + /// + /// The type of context to be configured. + /// The builder being used to configure the context. + /// + /// An existing to be used to connect to the database. If the connection is + /// in the open state then EF will not open or close the connection. If the connection is in the closed + /// state then EF will open and close the connection as needed. + /// + /// An optional action to allow additional Jet specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseJet( + [NotNull] this DbContextOptionsBuilder optionsBuilder, + [NotNull] DbConnection connection, + [CanBeNull] Action jetOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder) UseJet( + (DbContextOptionsBuilder) optionsBuilder, connection, jetOptionsAction); + // Note: Decision made to use DbConnection not SqlConnection: Issue #772 /// /// Configures the context to connect to a Microsoft Jet database. @@ -91,14 +231,16 @@ namespace Microsoft.EntityFrameworkCore if (jetConnection.DataAccessProviderFactory == null) { - throw new ArgumentException($"The {nameof(connection)} parameter of type {nameof(JetConnection)} must have its {nameof(JetConnection.DataAccessProviderFactory)} property set to OdbcFactory or OleDbFactory."); + var dataAccessProviderType = JetConnection.GetDataAccessProviderType(jetConnection.ConnectionString); + jetConnection.DataAccessProviderFactory = JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType); + jetConnection.Freeze(); } var extension = (JetOptionsExtension) GetOrCreateExtension(optionsBuilder) .WithConnection(connection); - + extension = extension.WithDataAccessProviderFactory(jetConnection.DataAccessProviderFactory); - + ((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(extension); ConfigureWarnings(optionsBuilder); @@ -108,63 +250,8 @@ namespace Microsoft.EntityFrameworkCore return optionsBuilder; } + #endregion - /// - /// Configures the context to connect to a Microsoft Jet database. - /// - /// The builder being used to configure the context. - /// The connection string of the database to connect to. - /// A `OdbcFactory` or `OleDbFactory` object to be used for all - /// data access operations by the Jet connection. - /// An optional action to allow additional Jet specific configuration. - /// The options builder so that further configuration can be chained. - internal static DbContextOptionsBuilder UseJetWithoutPredefinedDataAccessProviderFactory( - [NotNull] this DbContextOptionsBuilder optionsBuilder, - [NotNull] string connectionString, - [CanBeNull] Action jetOptionsAction = null) - where TContext : DbContext - => UseJetCore(optionsBuilder, connectionString, null, jetOptionsAction); - - /// - /// Configures the context to connect to a Microsoft Jet database. - /// - /// The type of context to be configured. - /// The builder being used to configure the context. - /// The connection string of the database to connect to. - /// A `OdbcFactory` or `OleDbFactory` object to be used for all - /// data access operations by the Jet connection. - /// An optional action to allow additional Jet specific configuration. - /// The options builder so that further configuration can be chained. - public static DbContextOptionsBuilder UseJet( - [NotNull] this DbContextOptionsBuilder optionsBuilder, - [NotNull] string connectionString, - [NotNull] DbProviderFactory dataAccessProviderFactory, - [CanBeNull] Action jetOptionsAction = null) - where TContext : DbContext - => (DbContextOptionsBuilder) UseJet( - (DbContextOptionsBuilder) optionsBuilder, connectionString, dataAccessProviderFactory, jetOptionsAction); - - // Note: Decision made to use DbConnection not SqlConnection: Issue #772 - /// - /// Configures the context to connect to a Microsoft Jet database. - /// - /// The type of context to be configured. - /// The builder being used to configure the context. - /// - /// An existing to be used to connect to the database. If the connection is - /// in the open state then EF will not open or close the connection. If the connection is in the closed - /// state then EF will open and close the connection as needed. - /// - /// An optional action to allow additional Jet specific configuration. - /// The options builder so that further configuration can be chained. - public static DbContextOptionsBuilder UseJet( - [NotNull] this DbContextOptionsBuilder optionsBuilder, - [NotNull] DbConnection connection, - [CanBeNull] Action jetOptionsAction = null) - where TContext : DbContext - => (DbContextOptionsBuilder) UseJet( - (DbContextOptionsBuilder) optionsBuilder, connection, jetOptionsAction); - private static JetOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.Options.FindExtension() ?? new JetOptionsExtension(); diff --git a/src/EFCore.Jet/Metadata/Conventions/JetConventionSetBuilder.cs b/src/EFCore.Jet/Metadata/Conventions/JetConventionSetBuilder.cs index 0b048bb..260be5b 100644 --- a/src/EFCore.Jet/Metadata/Conventions/JetConventionSetBuilder.cs +++ b/src/EFCore.Jet/Metadata/Conventions/JetConventionSetBuilder.cs @@ -56,7 +56,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions var serviceProvider = new ServiceCollection() .AddEntityFrameworkJet() .AddDbContext((p, o) => o - .UseJetWithoutPredefinedDataAccessProviderFactory( + .UseJetWithoutPredefinedDataAccessProvider( JetConnection.GetConnectionString("Jet.accdb", DataAccessProviderType.Odbc)) .UseInternalServiceProvider(p)) .BuildServiceProvider(); diff --git a/src/System.Data.Jet/JetConnection.cs b/src/System.Data.Jet/JetConnection.cs index 1decee8..7f61f85 100644 --- a/src/System.Data.Jet/JetConnection.cs +++ b/src/System.Data.Jet/JetConnection.cs @@ -10,6 +10,7 @@ namespace System.Data.Jet { private ConnectionState _state; private string _connectionString; + private bool _frozen; internal DbConnection InnerConnection { get; private set; } @@ -175,6 +176,10 @@ namespace System.Data.Jet { if (State != ConnectionState.Closed) throw new InvalidOperationException(Messages.CannotChangePropertyValueInThisConnectionState(nameof(ConnectionString), State)); + + if (_frozen) + throw new InvalidOperationException($"Cannot modify \"{nameof(ConnectionString)}\" property after the connection has been frozen."); + _connectionString = value; } } @@ -298,11 +303,15 @@ namespace System.Data.Jet return; if (string.IsNullOrWhiteSpace(_connectionString)) throw new InvalidOperationException(Messages.PropertyNotInitialized(nameof(ConnectionString))); - if (JetFactory == null) - throw new InvalidOperationException(Messages.PropertyNotInitialized(nameof(DataAccessProviderFactory))); if (State != ConnectionState.Closed) throw new InvalidOperationException(Messages.CannotCallMethodInThisConnectionState(nameof(Open), ConnectionState.Closed, State)); + if (JetFactory == null) + { + var dataAccessProviderType = GetDataAccessProviderType(ConnectionString); + DataAccessProviderFactory = JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType); + } + try { InnerConnection = InnerConnectionFactory.Instance.OpenConnection( @@ -404,6 +413,12 @@ namespace System.Data.Jet return clone; } + public void Freeze() + { + if (!string.IsNullOrWhiteSpace(ConnectionString)) + _frozen = true; + } + /// /// Clears the pool. ///