Skip to content

Commit 2f746e2

Browse files
authored
Merge pull request #47 from lauxjpn/feature/provider_detection
Implement automatic driver (ODBC) and provider (OLE DB) inference and detection
2 parents 9a58494 + 6141f1e commit 2f746e2

19 files changed

+446
-211
lines changed

build/dependencies.props

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
<MicrosoftBclHashCodeVersion>1.1.0</MicrosoftBclHashCodeVersion>
1515
<SystemCollectionsImmutableVersion>1.7.0</SystemCollectionsImmutableVersion>
1616
<SystemComponentModelAnnotationsVersion>4.7.0</SystemComponentModelAnnotationsVersion>
17-
<SystemDataOleDbVersion>5.0.0-preview.3.20171.3</SystemDataOleDbVersion>
18-
<SystemDataOdbcVersion>5.0.0-preview.3.20171.3</SystemDataOdbcVersion>
17+
<MicrosoftWin32Registry>4.7.0</MicrosoftWin32Registry>
18+
<SystemDataOleDbVersion>5.0.0-preview.3.20178.1</SystemDataOleDbVersion>
19+
<SystemDataOdbcVersion>5.0.0-preview.3.20178.1</SystemDataOdbcVersion>
1920
</PropertyGroup>
2021
<!-- System.Data.Jet -->
2122
<PropertyGroup>

src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs

+66-32
Original file line numberDiff line numberDiff line change
@@ -24,147 +24,166 @@ public static class JetDbContextOptionsBuilderExtensions
2424
/// Configures the context to connect to a Microsoft Jet database.
2525
/// </summary>
2626
/// <param name="optionsBuilder"> The builder being used to configure the context. </param>
27-
/// <param name="connectionString"> The connection string of the database to connect to. The underlying data
28-
/// access provider (ODBC or OLE DB) will be inferred from the style of this connection string. </param>
27+
/// <param name="fileNameOrConnectionString"> The file name or connection string of the database to connect to.
28+
/// If just a file name is supplied, the default data access provider type as defined by
29+
/// `JetConfiguration.DefaultDataAccessProviderType` is being used. If a connection string is supplied, the
30+
/// underlying data access provider (ODBC or OLE DB) will be inferred from the style of the connection string.
31+
/// In case the connection string does not specify an Access driver (ODBC) or ACE/Jet provider (OLE DB), the
32+
/// highest version of all compatible installed ones is being used. </param>
2933
/// <param name="jetOptionsAction">An optional action to allow additional Jet specific configuration.</param>
3034
/// <returns> The options builder so that further configuration can be chained. </returns>
3135
public static DbContextOptionsBuilder<TContext> UseJet<TContext>(
3236
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
33-
[NotNull] string connectionString,
37+
[NotNull] string fileNameOrConnectionString,
3438
[CanBeNull] Action<JetDbContextOptionsBuilder> jetOptionsAction = null)
3539
where TContext : DbContext
36-
=> (DbContextOptionsBuilder<TContext>) UseJet((DbContextOptionsBuilder) optionsBuilder, connectionString, jetOptionsAction);
40+
=> (DbContextOptionsBuilder<TContext>) UseJet((DbContextOptionsBuilder) optionsBuilder, fileNameOrConnectionString, jetOptionsAction);
3741

3842
/// <summary>
3943
/// Configures the context to connect to a Microsoft Jet database.
4044
/// </summary>
4145
/// <param name="optionsBuilder"> The builder being used to configure the context. </param>
42-
/// <param name="connectionString"> The connection string of the database to connect to. The underlying data
43-
/// access provider (ODBC or OLE DB) will be inferred from the style of this connection string. </param>
46+
/// <param name="fileNameOrConnectionString"> The file name or connection string of the database to connect to.
47+
/// If just a file name is supplied, the default data access provider type as defined by
48+
/// `JetConfiguration.DefaultDataAccessProviderType` is being used. If a connection string is supplied, the
49+
/// underlying data access provider (ODBC or OLE DB) will be inferred from the style of the connection string.
50+
/// In case the connection string does not specify an Access driver (ODBC) or ACE/Jet provider (OLE DB), the
51+
/// highest version of all compatible installed ones is being used. </param>
4452
/// <param name="jetOptionsAction">An optional action to allow additional Jet specific configuration.</param>
4553
/// <returns> The options builder so that further configuration can be chained. </returns>
4654
public static DbContextOptionsBuilder UseJet(
4755
[NotNull] this DbContextOptionsBuilder optionsBuilder,
48-
[NotNull] string connectionString,
56+
[NotNull] string fileNameOrConnectionString,
4957
[CanBeNull] Action<JetDbContextOptionsBuilder> jetOptionsAction = null)
5058
{
5159
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
52-
Check.NotEmpty(connectionString, nameof(connectionString));
60+
Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
5361

54-
return UseJetCore(optionsBuilder, connectionString, null, JetConnection.GetDataAccessProviderType(connectionString), jetOptionsAction);
62+
return UseJetCore(optionsBuilder, fileNameOrConnectionString, null, null, jetOptionsAction);
5563
}
5664

5765
/// <summary>
5866
/// Configures the context to connect to a Microsoft Jet database.
5967
/// </summary>
6068
/// <param name="optionsBuilder"> The builder being used to configure the context. </param>
61-
/// <param name="connectionString"> The connection string of the database to connect to. </param>
69+
/// <param name="fileNameOrConnectionString"> The file name or connection string of the database to connect to.
6270
/// <param name="dataAccessProviderFactory">An `OdbcFactory` or `OleDbFactory` object to be used for all
6371
/// data access operations by the Jet connection.</param>
6472
/// <param name="jetOptionsAction">An optional action to allow additional Jet specific configuration.</param>
6573
/// <returns> The options builder so that further configuration can be chained. </returns>
6674
public static DbContextOptionsBuilder<TContext> UseJet<TContext>(
6775
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
68-
[NotNull] string connectionString,
76+
[NotNull] string fileNameOrConnectionString,
6977
[NotNull] DbProviderFactory dataAccessProviderFactory,
7078
[CanBeNull] Action<JetDbContextOptionsBuilder> jetOptionsAction = null)
7179
where TContext : DbContext
7280
{
7381
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
74-
Check.NotEmpty(connectionString, nameof(connectionString));
82+
Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
7583
Check.NotNull(dataAccessProviderFactory, nameof(dataAccessProviderFactory));
7684

77-
return (DbContextOptionsBuilder<TContext>) UseJet((DbContextOptionsBuilder) optionsBuilder, connectionString, dataAccessProviderFactory, jetOptionsAction);
85+
return (DbContextOptionsBuilder<TContext>) UseJet((DbContextOptionsBuilder) optionsBuilder, fileNameOrConnectionString, dataAccessProviderFactory, jetOptionsAction);
7886
}
7987

8088
/// <summary>
8189
/// Configures the context to connect to a Microsoft Jet database.
8290
/// </summary>
8391
/// <param name="optionsBuilder"> The builder being used to configure the context. </param>
84-
/// <param name="connectionString"> The connection string of the database to connect to. </param>
92+
/// <param name="fileNameOrConnectionString"> The file name or connection string of the database to connect to.
8593
/// <param name="dataAccessProviderFactory">An `OdbcFactory` or `OleDbFactory` object to be used for all
8694
/// data access operations by the Jet connection.</param>
8795
/// <param name="jetOptionsAction">An optional action to allow additional Jet specific configuration.</param>
8896
/// <returns> The options builder so that further configuration can be chained. </returns>
8997
public static DbContextOptionsBuilder UseJet(
9098
[NotNull] this DbContextOptionsBuilder optionsBuilder,
91-
[NotNull] string connectionString,
99+
[NotNull] string fileNameOrConnectionString,
92100
[NotNull] DbProviderFactory dataAccessProviderFactory,
93101
[CanBeNull] Action<JetDbContextOptionsBuilder> jetOptionsAction = null)
94102
{
95103
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
96-
Check.NotEmpty(connectionString, nameof(connectionString));
104+
Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
97105
Check.NotNull(dataAccessProviderFactory, nameof(dataAccessProviderFactory));
98106

99-
return UseJetCore(optionsBuilder, connectionString, dataAccessProviderFactory, null, jetOptionsAction);
107+
return UseJetCore(optionsBuilder, fileNameOrConnectionString, dataAccessProviderFactory, null, jetOptionsAction);
100108
}
101109

102110
/// <summary>
103111
/// Configures the context to connect to a Microsoft Jet database.
104112
/// </summary>
105113
/// <param name="optionsBuilder"> The builder being used to configure the context. </param>
106-
/// <param name="connectionString"> The connection string of the database to connect to. </param>
114+
/// <param name="fileNameOrConnectionString"> The file name or connection string of the database to connect to.
107115
/// <param name="dataAccessProviderType">The type of the data access provider (`Odbc` or `OleDb`) to be used for all
108116
/// data access operations by the Jet connection.</param>
109117
/// <param name="jetOptionsAction">An optional action to allow additional Jet specific configuration.</param>
110118
/// <returns> The options builder so that further configuration can be chained. </returns>
111119
public static DbContextOptionsBuilder<TContext> UseJet<TContext>(
112120
[NotNull] this DbContextOptionsBuilder<TContext> optionsBuilder,
113-
[NotNull] string connectionString,
121+
[NotNull] string fileNameOrConnectionString,
114122
DataAccessProviderType dataAccessProviderType,
115123
[CanBeNull] Action<JetDbContextOptionsBuilder> jetOptionsAction = null)
116124
where TContext : DbContext
117125
{
118126
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
119-
Check.NotEmpty(connectionString, nameof(connectionString));
127+
Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
120128

121-
return (DbContextOptionsBuilder<TContext>) UseJet((DbContextOptionsBuilder)optionsBuilder, connectionString, dataAccessProviderType, jetOptionsAction);
129+
return (DbContextOptionsBuilder<TContext>) UseJet((DbContextOptionsBuilder)optionsBuilder, fileNameOrConnectionString, dataAccessProviderType, jetOptionsAction);
122130
}
123131

124132
/// <summary>
125133
/// Configures the context to connect to a Microsoft Jet database.
126134
/// </summary>
127135
/// <param name="optionsBuilder"> The builder being used to configure the context. </param>
128-
/// <param name="connectionString"> The connection string of the database to connect to. </param>
136+
/// <param name="fileNameOrConnectionString"> The file name or connection string of the database to connect to.
129137
/// <param name="dataAccessProviderType">The type of the data access provider (`Odbc` or `OleDb`) to be used for all
130138
/// data access operations by the Jet connection.</param>
131139
/// <param name="jetOptionsAction">An optional action to allow additional Jet specific configuration.</param>
132140
/// <returns> The options builder so that further configuration can be chained. </returns>
133141
public static DbContextOptionsBuilder UseJet(
134142
[NotNull] this DbContextOptionsBuilder optionsBuilder,
135-
[NotNull] string connectionString,
143+
[NotNull] string fileNameOrConnectionString,
136144
DataAccessProviderType dataAccessProviderType,
137145
[CanBeNull] Action<JetDbContextOptionsBuilder> jetOptionsAction = null)
138146
{
139147
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
140-
Check.NotEmpty(connectionString, nameof(connectionString));
148+
Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
141149

142-
return UseJetCore(optionsBuilder, connectionString, null, dataAccessProviderType, jetOptionsAction);
150+
return UseJetCore(optionsBuilder, fileNameOrConnectionString, null, dataAccessProviderType, jetOptionsAction);
143151
}
144152

145153
internal static DbContextOptionsBuilder UseJetWithoutPredefinedDataAccessProvider(
146154
[NotNull] this DbContextOptionsBuilder optionsBuilder,
147-
[NotNull] string connectionString,
155+
[NotNull] string fileNameOrConnectionString,
148156
[CanBeNull] Action<JetDbContextOptionsBuilder> jetOptionsAction = null)
149-
=> UseJetCore(optionsBuilder, connectionString, null, null, jetOptionsAction);
157+
=> UseJetCore(optionsBuilder, fileNameOrConnectionString, null, null, jetOptionsAction);
150158

151159
private static DbContextOptionsBuilder UseJetCore(
152160
[NotNull] DbContextOptionsBuilder optionsBuilder,
153-
[NotNull] string connectionString,
161+
[NotNull] string fileNameOrConnectionString,
154162
[CanBeNull] DbProviderFactory dataAccessProviderFactory,
155163
[CanBeNull] DataAccessProviderType? dataAccessProviderType,
156164
Action<JetDbContextOptionsBuilder> jetOptionsAction)
157165
{
158166
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
159-
Check.NotEmpty(connectionString, nameof(connectionString));
167+
Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
160168

161169
if (dataAccessProviderFactory == null && dataAccessProviderType == null)
162170
{
163-
throw new ArgumentException($"One of the parameters {nameof(dataAccessProviderFactory)} and {nameof(dataAccessProviderType)} must not be null.");
171+
if (JetConnection.IsConnectionString(fileNameOrConnectionString))
172+
{
173+
dataAccessProviderType = JetConnection.GetDataAccessProviderType(fileNameOrConnectionString);
174+
}
175+
else if (JetConnection.IsFileName(fileNameOrConnectionString))
176+
{
177+
dataAccessProviderType = JetConfiguration.DefaultDataAccessProviderType;
178+
}
179+
else
180+
{
181+
throw new ArgumentException($"Either {nameof(dataAccessProviderFactory)} or {nameof(dataAccessProviderType)} must not be null, or a file name must be specified for {nameof(fileNameOrConnectionString)}.");
182+
}
164183
}
165184

166185
var extension = (JetOptionsExtension) GetOrCreateExtension(optionsBuilder)
167-
.WithConnectionString(connectionString);
186+
.WithConnectionString(fileNameOrConnectionString);
168187

169188
extension = extension.WithDataAccessProviderFactory(
170189
dataAccessProviderFactory ?? JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType.Value));
@@ -231,7 +250,22 @@ public static DbContextOptionsBuilder UseJet(
231250

232251
if (jetConnection.DataAccessProviderFactory == null)
233252
{
234-
var dataAccessProviderType = JetConnection.GetDataAccessProviderType(jetConnection.ConnectionString);
253+
var fileNameOrConnectionString = jetConnection.ConnectionString;
254+
DataAccessProviderType dataAccessProviderType;
255+
256+
if (JetConnection.IsConnectionString(fileNameOrConnectionString))
257+
{
258+
dataAccessProviderType = JetConnection.GetDataAccessProviderType(fileNameOrConnectionString);
259+
}
260+
else if (JetConnection.IsFileName(fileNameOrConnectionString))
261+
{
262+
dataAccessProviderType = JetConfiguration.DefaultDataAccessProviderType;
263+
}
264+
else
265+
{
266+
throw new ArgumentException($"The data access provider type could not be inferred from the connections {nameof(JetConnection.DataAccessProviderFactory)} or {nameof(JetConnection.ConnectionString)} property and the {nameof(JetConnection.ConnectionString)} property is not a valid file name either.");
267+
}
268+
235269
jetConnection.DataAccessProviderFactory = JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType);
236270
jetConnection.Freeze();
237271
}

src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs

+6
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ protected override void Generate(
364364
Check.NotNull(operation, nameof(operation));
365365
Check.NotNull(builder, nameof(builder));
366366

367+
// CHECK: Rename table operations require extensions like ADOX or DAO.
368+
// A native way to do this would be to:
369+
// 1. CREATE TABLE `destination table`
370+
// 2. INSERT INTO ... SELECT ... FROM
371+
// 3. DROP TABLE `source table`
372+
// 4. Recrete indices and references.
367373
builder.Append("RENAME TABLE ")
368374
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name))
369375
.Append(" TO ")

src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class JetDatabaseModelFactory : DatabaseModelFactory
3434
private Dictionary<string, DatabaseColumn> _tableColumns;
3535

3636
private static string ObjectKey([NotNull] string name)
37-
=> "[" + name + "]";
37+
=> "`" + name + "`";
3838

3939
private static string TableKey(DatabaseTable table)
4040
=> TableKey(table.Name);
@@ -48,6 +48,7 @@ private static string ColumnKey(DatabaseTable table, string columnName)
4848
private static readonly List<string> _tablePatterns = new List<string>
4949
{
5050
"{table}",
51+
"`{table}`",
5152
"[{table}]"
5253
};
5354

0 commit comments

Comments
 (0)