diff --git a/build/dependencies.props b/build/dependencies.props
index e7d16ef8..282d031a 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -14,8 +14,9 @@
1.1.0
1.7.0
4.7.0
- 5.0.0-preview.3.20171.3
- 5.0.0-preview.3.20171.3
+ 4.7.0
+ 5.0.0-preview.3.20178.1
+ 5.0.0-preview.3.20178.1
diff --git a/src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs b/src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs
index 1fa85f53..ef9719bd 100644
--- a/src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs
+++ b/src/EFCore.Jet/Extensions/JetDbContextOptionsBuilderExtensions.cs
@@ -24,147 +24,166 @@ public static class JetDbContextOptionsBuilderExtensions
/// 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.
+ /// The file name or connection string of the database to connect to.
+ /// If just a file name is supplied, the default data access provider type as defined by
+ /// `JetConfiguration.DefaultDataAccessProviderType` is being used. If a connection string is supplied, the
+ /// underlying data access provider (ODBC or OLE DB) will be inferred from the style of the connection string.
+ /// In case the connection string does not specify an Access driver (ODBC) or ACE/Jet provider (OLE DB), the
+ /// highest version of all compatible installed ones is being used.
/// 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] string fileNameOrConnectionString,
[CanBeNull] Action jetOptionsAction = null)
where TContext : DbContext
- => (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder) optionsBuilder, connectionString, jetOptionsAction);
+ => (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder) optionsBuilder, fileNameOrConnectionString, 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.
+ /// The file name or connection string of the database to connect to.
+ /// If just a file name is supplied, the default data access provider type as defined by
+ /// `JetConfiguration.DefaultDataAccessProviderType` is being used. If a connection string is supplied, the
+ /// underlying data access provider (ODBC or OLE DB) will be inferred from the style of the connection string.
+ /// In case the connection string does not specify an Access driver (ODBC) or ACE/Jet provider (OLE DB), the
+ /// highest version of all compatible installed ones is being used.
/// 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] string fileNameOrConnectionString,
[CanBeNull] Action jetOptionsAction = null)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
- Check.NotEmpty(connectionString, nameof(connectionString));
+ Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
- return UseJetCore(optionsBuilder, connectionString, null, JetConnection.GetDataAccessProviderType(connectionString), jetOptionsAction);
+ return UseJetCore(optionsBuilder, fileNameOrConnectionString, null, 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 file name or 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.
public static DbContextOptionsBuilder UseJet(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
- [NotNull] string connectionString,
+ [NotNull] string fileNameOrConnectionString,
[NotNull] DbProviderFactory dataAccessProviderFactory,
[CanBeNull] Action jetOptionsAction = null)
where TContext : DbContext
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
- Check.NotEmpty(connectionString, nameof(connectionString));
+ Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
Check.NotNull(dataAccessProviderFactory, nameof(dataAccessProviderFactory));
- return (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder) optionsBuilder, connectionString, dataAccessProviderFactory, jetOptionsAction);
+ return (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder) optionsBuilder, fileNameOrConnectionString, 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.
+ /// The file name or 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.
public static DbContextOptionsBuilder UseJet(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
- [NotNull] string connectionString,
+ [NotNull] string fileNameOrConnectionString,
[NotNull] DbProviderFactory dataAccessProviderFactory,
[CanBeNull] Action jetOptionsAction = null)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
- Check.NotEmpty(connectionString, nameof(connectionString));
+ Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
Check.NotNull(dataAccessProviderFactory, nameof(dataAccessProviderFactory));
- return UseJetCore(optionsBuilder, connectionString, dataAccessProviderFactory, null, jetOptionsAction);
+ return UseJetCore(optionsBuilder, fileNameOrConnectionString, 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 file name or 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,
+ [NotNull] string fileNameOrConnectionString,
DataAccessProviderType dataAccessProviderType,
[CanBeNull] Action jetOptionsAction = null)
where TContext : DbContext
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
- Check.NotEmpty(connectionString, nameof(connectionString));
+ Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
- return (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder)optionsBuilder, connectionString, dataAccessProviderType, jetOptionsAction);
+ return (DbContextOptionsBuilder) UseJet((DbContextOptionsBuilder)optionsBuilder, fileNameOrConnectionString, 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 file name or 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,
+ [NotNull] string fileNameOrConnectionString,
DataAccessProviderType dataAccessProviderType,
[CanBeNull] Action jetOptionsAction = null)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
- Check.NotEmpty(connectionString, nameof(connectionString));
+ Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
- return UseJetCore(optionsBuilder, connectionString, null, dataAccessProviderType, jetOptionsAction);
+ return UseJetCore(optionsBuilder, fileNameOrConnectionString, null, dataAccessProviderType, jetOptionsAction);
}
internal static DbContextOptionsBuilder UseJetWithoutPredefinedDataAccessProvider(
[NotNull] this DbContextOptionsBuilder optionsBuilder,
- [NotNull] string connectionString,
+ [NotNull] string fileNameOrConnectionString,
[CanBeNull] Action jetOptionsAction = null)
- => UseJetCore(optionsBuilder, connectionString, null, null, jetOptionsAction);
+ => UseJetCore(optionsBuilder, fileNameOrConnectionString, null, null, jetOptionsAction);
private static DbContextOptionsBuilder UseJetCore(
[NotNull] DbContextOptionsBuilder optionsBuilder,
- [NotNull] string connectionString,
+ [NotNull] string fileNameOrConnectionString,
[CanBeNull] DbProviderFactory dataAccessProviderFactory,
[CanBeNull] DataAccessProviderType? dataAccessProviderType,
Action jetOptionsAction)
{
Check.NotNull(optionsBuilder, nameof(optionsBuilder));
- Check.NotEmpty(connectionString, nameof(connectionString));
+ Check.NotEmpty(fileNameOrConnectionString, nameof(fileNameOrConnectionString));
if (dataAccessProviderFactory == null && dataAccessProviderType == null)
{
- throw new ArgumentException($"One of the parameters {nameof(dataAccessProviderFactory)} and {nameof(dataAccessProviderType)} must not be null.");
+ if (JetConnection.IsConnectionString(fileNameOrConnectionString))
+ {
+ dataAccessProviderType = JetConnection.GetDataAccessProviderType(fileNameOrConnectionString);
+ }
+ else if (JetConnection.IsFileName(fileNameOrConnectionString))
+ {
+ dataAccessProviderType = JetConfiguration.DefaultDataAccessProviderType;
+ }
+ else
+ {
+ throw new ArgumentException($"Either {nameof(dataAccessProviderFactory)} or {nameof(dataAccessProviderType)} must not be null, or a file name must be specified for {nameof(fileNameOrConnectionString)}.");
+ }
}
var extension = (JetOptionsExtension) GetOrCreateExtension(optionsBuilder)
- .WithConnectionString(connectionString);
+ .WithConnectionString(fileNameOrConnectionString);
extension = extension.WithDataAccessProviderFactory(
dataAccessProviderFactory ?? JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType.Value));
@@ -231,7 +250,22 @@ public static DbContextOptionsBuilder UseJet(
if (jetConnection.DataAccessProviderFactory == null)
{
- var dataAccessProviderType = JetConnection.GetDataAccessProviderType(jetConnection.ConnectionString);
+ var fileNameOrConnectionString = jetConnection.ConnectionString;
+ DataAccessProviderType dataAccessProviderType;
+
+ if (JetConnection.IsConnectionString(fileNameOrConnectionString))
+ {
+ dataAccessProviderType = JetConnection.GetDataAccessProviderType(fileNameOrConnectionString);
+ }
+ else if (JetConnection.IsFileName(fileNameOrConnectionString))
+ {
+ dataAccessProviderType = JetConfiguration.DefaultDataAccessProviderType;
+ }
+ else
+ {
+ 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.");
+ }
+
jetConnection.DataAccessProviderFactory = JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType);
jetConnection.Freeze();
}
diff --git a/src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs b/src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs
index c7644b6c..cc11d0ff 100644
--- a/src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs
+++ b/src/EFCore.Jet/Migrations/JetMigrationsSqlGenerator.cs
@@ -364,6 +364,12 @@ protected override void Generate(
Check.NotNull(operation, nameof(operation));
Check.NotNull(builder, nameof(builder));
+ // CHECK: Rename table operations require extensions like ADOX or DAO.
+ // A native way to do this would be to:
+ // 1. CREATE TABLE `destination table`
+ // 2. INSERT INTO ... SELECT ... FROM
+ // 3. DROP TABLE `source table`
+ // 4. Recrete indices and references.
builder.Append("RENAME TABLE ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name))
.Append(" TO ")
diff --git a/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs b/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
index 332f9371..eddc5604 100644
--- a/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
+++ b/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
@@ -34,7 +34,7 @@ public class JetDatabaseModelFactory : DatabaseModelFactory
private Dictionary _tableColumns;
private static string ObjectKey([NotNull] string name)
- => "[" + name + "]";
+ => "`" + name + "`";
private static string TableKey(DatabaseTable table)
=> TableKey(table.Name);
@@ -48,6 +48,7 @@ private static string ColumnKey(DatabaseTable table, string columnName)
private static readonly List _tablePatterns = new List
{
"{table}",
+ "`{table}`",
"[{table}]"
};
diff --git a/src/System.Data.Jet/AdoxWrapper.cs b/src/System.Data.Jet/AdoxWrapper.cs
index fd8c857f..b1f0de43 100644
--- a/src/System.Data.Jet/AdoxWrapper.cs
+++ b/src/System.Data.Jet/AdoxWrapper.cs
@@ -1,4 +1,5 @@
using System.Data.Common;
+using System.Data.Jet.JetStoreSchemaDefinition;
namespace System.Data.Jet
{
@@ -23,6 +24,8 @@ public static void RenameTable(string connectionString, string tableName, string
}
catch (Exception e)
{
+ // TODO: Try interating over the _Tables collection instead of using Item["TableName"].
+
throw new Exception("Cannot rename table", e);
}
finally
@@ -47,7 +50,8 @@ public static void RenameColumn(string connectionString, string tableName, strin
try
{
using var tables = catalog.Tables;
- using var columns = tables[tableName].Columns;
+ using var table = tables[tableName];
+ using var columns = table.Columns;
using var column = columns[columnName];
column.Name = newColumnName;
}
@@ -61,8 +65,10 @@ public static void RenameColumn(string connectionString, string tableName, strin
}
}
- public static string CreateEmptyDatabase(string fileName, DbProviderFactory dataAccessProviderFactory)
+ public static string CreateEmptyDatabase(string fileNameOrConnectionString, DbProviderFactory dataAccessProviderFactory)
{
+ var fileName = JetStoreDatabaseHandling.ExpandFileName(JetStoreDatabaseHandling.ExtractFileNameFromConnectionString(fileNameOrConnectionString));
+
string connectionString = null;
using var catalog = GetCatalogInstance();
@@ -77,7 +83,7 @@ public static string CreateEmptyDatabase(string fileName, DbProviderFactory data
}
catch (Exception e)
{
- throw new Exception("Cannot create database using the specified connection string: " + connectionString, e);
+ throw new Exception($"Cannot create database \"{fileName}\" using ADOX with the following connection string: " + connectionString, e);
}
try
@@ -89,8 +95,7 @@ public static string CreateEmptyDatabase(string fileName, DbProviderFactory data
connection.DataAccessProviderFactory = dataAccessProviderFactory;
connection.Open();
- string sql = @"
-CREATE TABLE `MSysAccessStorage` (
+ var sql = @"CREATE TABLE `MSysAccessStorage` (
`DateCreate` DATETIME NULL,
`DateUpdate` DATETIME NULL,
`Id` COUNTER NOT NULL,
@@ -108,7 +113,7 @@ public static string CreateEmptyDatabase(string fileName, DbProviderFactory data
}
catch (Exception e)
{
- throw new Exception("Cannot create database using the specified connection string.", e);
+ throw new Exception($"Cannot setup the newly created database \"{fileName}\" using {Enum.GetName(typeof(DataAccessProviderType), JetConnection.GetDataAccessProviderType(dataAccessProviderFactory))} with the following connection string: " + connectionString, e);
}
try
@@ -133,9 +138,9 @@ private static dynamic GetCatalogInstanceAndOpen(string errorPrefix, string conn
try
{
- using dynamic cnn = new ComObject("ADODB.Connection");
- cnn.Open(connectionString);
- catalog.ActiveConnection = cnn;
+ using dynamic connection = new ComObject("ADODB.Connection");
+ connection.Open(connectionString);
+ catalog.ActiveConnection = connection;
}
catch (Exception e)
{
diff --git a/src/System.Data.Jet/ComObject.cs b/src/System.Data.Jet/ComObject.cs
index 4ab5e913..a6c3676f 100644
--- a/src/System.Data.Jet/ComObject.cs
+++ b/src/System.Data.Jet/ComObject.cs
@@ -110,7 +110,7 @@ private static object WrapIfRequired(object obj)
public void Dispose()
{
- // The RCW is a .NET object and cannot be released from the finalizer anymore,
+ // The RCW is a .NET object and cannot be released from the finalizer,
// because it might not exist anymore.
if (_instance != null)
{
diff --git a/src/System.Data.Jet/DbConnectionStringBuilderExtensions.cs b/src/System.Data.Jet/DbConnectionStringBuilderExtensions.cs
index c1306d4e..716d6dbd 100644
--- a/src/System.Data.Jet/DbConnectionStringBuilderExtensions.cs
+++ b/src/System.Data.Jet/DbConnectionStringBuilderExtensions.cs
@@ -29,7 +29,7 @@ public static void SetProvider(this DbConnectionStringBuilder builder, string va
}
else if (IsOdbc(builder))
{
- builder["driver"] = Regex.Replace(value.Trim(), @"^(?@.*)\b", RegexOptions.IgnoreCase);
- private static readonly Regex _selectRowCountRegularExpression = new Regex(@"^\s*select\s*@@rowcount\s*[;]?\s*$", RegexOptions.IgnoreCase);
+ private static readonly Regex _selectRowCountRegularExpression = new Regex(@"^\s*select\s*@@rowcount\s*;?\s*$", RegexOptions.IgnoreCase);
private static readonly Regex _ifStatementRegex = new Regex(@"^\s*if\s*(?not)?\s*exists\s*\((?.+)\)\s*then\s*(?.*)$", RegexOptions.IgnoreCase);
protected JetCommand(JetCommand source)
diff --git a/src/System.Data.Jet/JetConfiguration.cs b/src/System.Data.Jet/JetConfiguration.cs
index 0567a087..01c8d03a 100644
--- a/src/System.Data.Jet/JetConfiguration.cs
+++ b/src/System.Data.Jet/JetConfiguration.cs
@@ -1,8 +1,4 @@
-using System.Data.Common;
-
-// ReSharper disable InconsistentNaming
-
-namespace System.Data.Jet
+namespace System.Data.Jet
{
///
/// Jet configuration
@@ -12,10 +8,11 @@ public static class JetConfiguration
///
/// The time span offset (Jet does not support timespans)
///
- public static DateTime TimeSpanOffset = new DateTime(1899, 12, 30);
+ public static DateTime TimeSpanOffset { get; set; } = new DateTime(1899, 12, 30);
private static object _integerNullValue = Int32.MinValue;
+ // CHECK: Replace with Nullable
///
/// Gets or sets the integer null value returned by queries. This should solve a Jet issue
/// that if I do a UNION ALL of null, int and null the Jet raises an error
@@ -25,7 +22,7 @@ public static class JetConfiguration
///
public static object IntegerNullValue
{
- get { return _integerNullValue; }
+ get => _integerNullValue;
set
{
if (!(value is int) && value != null)
@@ -33,13 +30,8 @@ public static object IntegerNullValue
_integerNullValue = value;
}
}
-
- // "Microsoft.ACE.OLEDB.12.0" should have the fewest problems of all versions, as it supports 32 Bit and 64 Bit,
- // does not throw an AccessViolationException when connecting through OLE DB without connection pooling enabled,
- // has the highest backwards compatibility level of all recent releases and can freely be downloaded from
- // Microsoft as "Microsoft Access Database Engine 2010 Redistributable".
- public static string OleDbDefaultProvider = "Microsoft.ACE.OLEDB.12.0";
- public static string OdbcDefaultProvider = "Microsoft Access Driver (*.mdb, *.accdb)";
+
+ public static DataAccessProviderType DefaultDataAccessProviderType { get; set; } = DataAccessProviderType.OleDb;
// The SQL statement
//
@@ -63,7 +55,7 @@ public static object IntegerNullValue
///
/// The DUAL table or query
///
- public static string DUAL = DUALForAccdb;
+ public static string DUAL { get; set; } = DUALForAccdb;
///
/// The dual table for accdb
@@ -81,7 +73,7 @@ public static object IntegerNullValue
///
/// true to show SQL statements; otherwise, false.
///
- public static bool ShowSqlStatements = false;
+ public static bool ShowSqlStatements { get; set; } = false;
///
/// Gets or sets a value indicating whether the connection pooling should be used
@@ -89,6 +81,6 @@ public static object IntegerNullValue
///
/// true to use the connection pooling; otherwise, false.
///
- public static bool UseConnectionPooling = false;
+ public static bool UseConnectionPooling { get; set; } = false;
}
}
\ No newline at end of file
diff --git a/src/System.Data.Jet/JetConnection.cs b/src/System.Data.Jet/JetConnection.cs
index 7f61f852..9c1cdd5f 100644
--- a/src/System.Data.Jet/JetConnection.cs
+++ b/src/System.Data.Jet/JetConnection.cs
@@ -1,8 +1,11 @@
+using System.Collections.Generic;
using System.Data.Common;
using System.Data.Jet.JetStoreSchemaDefinition;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
+using Microsoft.Win32;
namespace System.Data.Jet
{
@@ -27,9 +30,9 @@ public JetConnection()
///
/// Initializes a new instance of the class.
///
- /// The connection string.
- public JetConnection(string connectionString)
- : this(connectionString, null)
+ /// The file name or connection string (either ODBC or OLE DB).
+ public JetConnection(string fileNameOrConnectionString)
+ : this(fileNameOrConnectionString, null)
{
}
@@ -46,12 +49,12 @@ public JetConnection(DbProviderFactory dataAccessProviderFactory)
///
/// Initializes a new instance of the class.
///
- /// The connection string.
+ /// The file name or connection string (either ODBC or OLE DB).
/// The underlying provider factory to use by Jet. Supported are
/// `OdbcFactory` and `OleDbFactory`.
- public JetConnection(string connectionString, DbProviderFactory dataAccessProviderFactory)
+ public JetConnection(string fileNameOrConnectionString, DbProviderFactory dataAccessProviderFactory)
{
- ConnectionString = connectionString;
+ ConnectionString = fileNameOrConnectionString;
if (dataAccessProviderFactory != null)
DataAccessProviderFactory = dataAccessProviderFactory;
@@ -80,7 +83,7 @@ public JetConnection(string connectionString, DbProviderFactory dataAccessProvid
/// This property can only be set as long as the connection is closed.
public DbProviderFactory DataAccessProviderFactory
{
- get => JetFactory.InnerFactory;
+ get => JetFactory?.InnerFactory;
set
{
if (value == null)
@@ -176,10 +179,10 @@ public override string ConnectionString
{
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;
}
}
@@ -216,7 +219,14 @@ public override string Database
/// Gets the name of the file to open.
///
public override string DataSource
- => GetDatabaseFilePath(_connectionString);
+ {
+ get
+ {
+ var connectionStringBuilder = JetFactory.InnerFactory.CreateConnectionStringBuilder();
+ connectionStringBuilder.ConnectionString = _connectionString;
+ return connectionStringBuilder.GetDataSource();
+ }
+ }
///
/// Releases the unmanaged resources used by the and optionally releases the managed resources.
@@ -306,16 +316,58 @@ public override void Open()
if (State != ConnectionState.Closed)
throw new InvalidOperationException(Messages.CannotCallMethodInThisConnectionState(nameof(Open), ConnectionState.Closed, State));
- if (JetFactory == null)
+ var connectionString = ConnectionString;
+ var dataAccessProviderFactory = DataAccessProviderFactory;
+ var dataAccessProviderType = dataAccessProviderFactory == null
+ ? JetConfiguration.DefaultDataAccessProviderType
+ : GetDataAccessProviderType(dataAccessProviderFactory);
+
+ dataAccessProviderFactory ??= JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType);
+
+ // If the connection string is just a file path, we need to construct a valid connection string from it
+ // by using the default data access provider type (ODBC or OLE DB) and retrieving its most recent
+ // ACE/Jet provider.
+ if (IsFileName(connectionString))
+ {
+ var fileName = connectionString;
+ connectionString = GetConnectionString(fileName, dataAccessProviderType);
+ }
+
+ // It is possible, that a connection string was provided, that left out the actual ACE/Jet provider
+ // information, but is in a distinctive style (ODBC or OLE DB) anyway.
+ // We need to retrieving the data access provider type's most recent ACE/Jet provider in that case.
+ var connectionStringBuilder = dataAccessProviderFactory.CreateConnectionStringBuilder();
+ connectionStringBuilder.ConnectionString = connectionString;
+
+ if (string.IsNullOrWhiteSpace(connectionStringBuilder.GetProvider()))
+ {
+ var provider = GetMostRecentCompatibleProviders(dataAccessProviderType)
+ .FirstOrDefault()
+ .Key;
+
+ if (provider == null)
+ throw new InvalidOperationException($"Unable to find any compatible {Enum.GetName(typeof(DataAccessProviderType), dataAccessProviderType)} provider for the connection string: {connectionString}");
+
+ connectionStringBuilder.SetProvider(provider);
+ connectionString = connectionStringBuilder.ToString();
+ }
+
+ // Enable ExtendedAnsiSQL when using ODBC to support ODBC 4.0 statements (like CREATE VIEW).
+ if (dataAccessProviderType == DataAccessProviderType.Odbc)
{
- var dataAccessProviderType = GetDataAccessProviderType(ConnectionString);
- DataAccessProviderFactory = JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType);
+ if (!connectionStringBuilder.ContainsKey("ExtendedAnsiSQL"))
+ {
+ connectionStringBuilder["ExtendedAnsiSQL"] = 1;
+ connectionString = connectionStringBuilder.ToString();
+ }
}
+ DataAccessProviderFactory ??= dataAccessProviderFactory;
+
try
{
InnerConnection = InnerConnectionFactory.Instance.OpenConnection(
- ExpandDatabaseFilePath(_connectionString),
+ ExpandDatabaseFilePath(connectionString),
JetFactory.InnerFactory);
InnerConnection.StateChange += WrappedConnection_StateChange;
@@ -365,8 +417,7 @@ public bool TableExists(string tableName)
try
{
- var sqlFormat = "select count(*) from [{0}] where 1=2";
- CreateCommand(string.Format(sqlFormat, tableName))
+ CreateCommand($"select count(*) from `{tableName}` where 1=2")
.ExecuteNonQuery();
tableExists = true;
}
@@ -437,41 +488,36 @@ public static void ClearAllPools()
public void CreateEmptyDatabase()
=> CreateEmptyDatabase(DataSource, DataAccessProviderFactory);
- public static string CreateEmptyDatabase(string fileName, DbProviderFactory dataAccessProviderFactory)
- => AdoxWrapper.CreateEmptyDatabase(fileName, dataAccessProviderFactory);
+ // TODO: Use the `CREATE_DB` connection string option instead of calling ADOX when using ODBC, to create
+ // a new database file.
+ // Alternatively, use DAO in conjunction with ODBC.
+ public static string CreateEmptyDatabase(string fileNameOrConnectionString, DbProviderFactory dataAccessProviderFactory)
+ => AdoxWrapper.CreateEmptyDatabase(fileNameOrConnectionString, dataAccessProviderFactory);
public static string GetConnectionString(string fileName, DbProviderFactory dataAccessProviderFactory)
=> GetConnectionString(fileName, GetDataAccessProviderType(dataAccessProviderFactory));
public static string GetConnectionString(string fileName, DataAccessProviderType dataAccessProviderType)
=> GetConnectionString(
- dataAccessProviderType == DataAccessProviderType.OleDb
- ? JetConfiguration.OleDbDefaultProvider
- : JetConfiguration.OdbcDefaultProvider,
+ GetMostRecentCompatibleProviders(dataAccessProviderType).First().Key,
fileName,
dataAccessProviderType);
public static string GetConnectionString(string provider, string fileName, DbProviderFactory dataAccessProviderFactory)
=> GetConnectionString(provider, fileName, GetDataAccessProviderType(dataAccessProviderFactory));
-
+
public static string GetConnectionString(string provider, string fileName, DataAccessProviderType dataAccessProviderType)
=> dataAccessProviderType == DataAccessProviderType.OleDb
? $"Provider={provider};Data Source={fileName}"
: $"Driver={{{provider}}};DBQ={fileName}";
- private string GetDatabaseFilePath(string connectionString)
- {
- var connectionStringBuilder = JetFactory.InnerFactory.CreateConnectionStringBuilder();
- connectionStringBuilder.ConnectionString = connectionString;
- return connectionStringBuilder.GetDataSource();
- }
-
private string ExpandDatabaseFilePath(string connectionString)
{
var connectionStringBuilder = JetFactory.InnerFactory.CreateConnectionStringBuilder();
connectionStringBuilder.ConnectionString = connectionString;
connectionStringBuilder.SetDataSource(JetStoreDatabaseHandling.ExpandFileName(connectionStringBuilder.GetDataSource()));
- return connectionStringBuilder.ConnectionString;
+
+ return connectionStringBuilder.ToString();
}
public void DropDatabase()
@@ -503,14 +549,23 @@ public static bool DatabaseExists(string fileNameOrConnectionString)
public static DataAccessProviderType GetDataAccessProviderType(string connectionString)
{
- var isOleDb = Regex.IsMatch(connectionString, @"Provider\s*=\s*\w+", RegexOptions.IgnoreCase);
- var isOdbc = Regex.IsMatch(connectionString, @"Driver\s*=\s*\{?\w+\}?", RegexOptions.IgnoreCase);
+ var isOleDb = Regex.IsMatch(connectionString, @"^(?:.*;)?\s*Provider\s*=\s*\w+", RegexOptions.IgnoreCase);
+ var isOdbc = Regex.IsMatch(connectionString, @"^(?:.*;)?Driver\s*=\s*\{?\w+\}?", RegexOptions.IgnoreCase);
if (isOdbc && isOleDb)
throw new InvalidOperationException("The connection string appears to be for ODBC and OLE DB. Only one distinct style is supported at a time.");
if (!isOdbc && !isOleDb)
- throw new ArgumentException("The connection string appears to be neither ODBC nor OLE DB compliant.", nameof(connectionString));
+ {
+ isOleDb = Regex.IsMatch(connectionString, @"^(?:.*;)?\s*Data Source\s*=", RegexOptions.IgnoreCase);
+ isOdbc = Regex.IsMatch(connectionString, @"^(?:.*;)?\s*DBQ\s*=", RegexOptions.IgnoreCase);
+
+ if (isOdbc && isOleDb)
+ throw new InvalidOperationException("The connection string appears to be for ODBC and OLE DB. Only one distinct style is supported at a time.");
+
+ if (!isOdbc && !isOleDb)
+ throw new ArgumentException("The connection string appears to be neither ODBC nor OLE DB compliant.", nameof(connectionString));
+ }
return isOleDb
? DataAccessProviderType.OleDb
@@ -547,5 +602,117 @@ public static DataAccessProviderType GetDataAccessProviderType(DbProviderFactory
? DataAccessProviderType.OleDb
: DataAccessProviderType.Odbc;
}
+
+ public static KeyValuePair[] GetMostRecentCompatibleProviders(DataAccessProviderType dataAccessProviderType)
+ => dataAccessProviderType == DataAccessProviderType.OleDb
+ ? _oledbProviders.Value
+ .Select(s => Regex.Match(s, @"Microsoft\.(?:ACE|Jet)\.OLEDB\.(?\d+\.\d+)", RegexOptions.IgnoreCase))
+ .Where(m => m.Success)
+ .Select(
+ m => new KeyValuePair(
+ m.Value, new Version(
+ m.Groups["Version"]
+ .Value)))
+ .OrderByDescending(kvp => kvp.Value)
+ .ToArray()
+ : _odbcProviders.Value
+ .Where(
+ kvp => kvp.Key.IndexOf("Microsoft Access Driver", StringComparison.OrdinalIgnoreCase) >= 0 &&
+ (kvp.Key.IndexOf("*.mdb", StringComparison.OrdinalIgnoreCase) >= 0 || kvp.Key.IndexOf("*.accdb", StringComparison.OrdinalIgnoreCase) >= 0))
+ .OrderByDescending(kvp => kvp.Value)
+ .ThenByDescending(kvp => kvp.Key.IndexOf("*.accdb", StringComparison.OrdinalIgnoreCase))
+ .ToArray();
+
+ private static readonly Lazy> _odbcProviders = new Lazy>(() =>
+ {
+ var drivers = new Dictionary();
+
+ try
+ {
+ using (var odbcKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\ODBC\ODBCINST.INI"))
+ {
+ if (odbcKey != null)
+ {
+ var driverKeyNames = odbcKey.GetSubKeyNames();
+
+ foreach (var driverKeyName in driverKeyNames)
+ {
+ try
+ {
+ using (var driverKey = odbcKey.OpenSubKey(driverKeyName))
+ {
+ if (driverKey?.GetValue("Driver") != null)
+ {
+ if (driverKey.GetValue("DriverODBCVer") is string versionString)
+ {
+ drivers.Add(driverKeyName, new Version(versionString));
+ }
+ }
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ }
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+
+ return drivers;
+ }, true);
+
+ private static readonly Lazy _oledbProviders = new Lazy(() =>
+ {
+ var providers = new List();
+
+ try
+ {
+ using (var clsidKey = Registry.ClassesRoot.OpenSubKey(@"CLSID"))
+ {
+ if (clsidKey != null)
+ {
+ var clasidSubKeyNames = clsidKey.GetSubKeyNames();
+
+ foreach (var clsidSubKeyName in clasidSubKeyNames)
+ {
+ try
+ {
+ using (var clsidSubKey = clsidKey.OpenSubKey(clsidSubKeyName))
+ {
+ if (clsidSubKey?.GetValue("OLEDB_SERVICES") != null)
+ {
+ if (clsidSubKey.GetValue(null) is string provider)
+ {
+ providers.Add(provider);
+ }
+ }
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+ }
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+
+ return providers.ToArray();
+ }, true);
+
+ public static bool IsConnectionString(string fileNameOrConnectionString)
+ => JetStoreDatabaseHandling.IsConnectionString(fileNameOrConnectionString);
+
+ public static bool IsFileName(string fileNameOrConnectionString)
+ => JetStoreDatabaseHandling.IsFileName(fileNameOrConnectionString);
}
}
\ No newline at end of file
diff --git a/src/System.Data.Jet/JetDataReader.cs b/src/System.Data.Jet/JetDataReader.cs
index 8c6d6dbd..9695d5b8 100644
--- a/src/System.Data.Jet/JetDataReader.cs
+++ b/src/System.Data.Jet/JetDataReader.cs
@@ -1,12 +1,10 @@
-using System;
-using System.Data.Common;
+using System.Data.Common;
using System.Text;
using System.Threading;
-using System.Threading.Tasks;
namespace System.Data.Jet
{
- class JetDataReader : DbDataReader
+ internal class JetDataReader : DbDataReader
{
#if DEBUG
private static int _activeObjectsCount;
@@ -24,15 +22,15 @@ public JetDataReader(DbDataReader dataReader, int topCount, int skipCount)
: this(dataReader)
{
_topCount = topCount;
- for (int i = 0; i < skipCount; i++)
+ for (var i = 0; i < skipCount; i++)
{
_wrappedDataReader.Read();
}
}
- private DbDataReader _wrappedDataReader;
- private readonly int _topCount = 0;
- private int _readCount = 0;
+ private readonly DbDataReader _wrappedDataReader;
+ private readonly int _topCount;
+ private int _readCount;
public override void Close()
{
@@ -43,64 +41,81 @@ public override void Close()
}
public override int Depth
- {
- get { return _wrappedDataReader.Depth; }
- }
+ => _wrappedDataReader.Depth;
public override int FieldCount
- {
- get { return _wrappedDataReader.FieldCount; }
- }
+ => _wrappedDataReader.FieldCount;
public override bool GetBoolean(int ordinal)
{
- object booleanObject = _wrappedDataReader.GetValue(ordinal);
- if (booleanObject == null)
- throw new InvalidOperationException("Cannot cast null to boolean");
- if (booleanObject is bool)
- return _wrappedDataReader.GetBoolean(ordinal);
- else if (booleanObject is short)
- return ((short) booleanObject) != 0;
- else
- throw new InvalidOperationException(string.Format("Cannot convert {0} to boolean", booleanObject.GetType()));
+ var value = _wrappedDataReader.GetValue(ordinal);
+ if (value is bool boolValue)
+ return boolValue;
+
+ if (value is sbyte sbyteValue)
+ return sbyteValue != 0;
+ if (value is byte byteValue)
+ return byteValue != 0;
+ if (value is short shortValue)
+ return shortValue != 0;
+ if (value is ushort ushortValue)
+ return ushortValue != 0;
+ if (value is int intValue)
+ return intValue != 0;
+ if (value is uint uintValue)
+ return uintValue != 0;
+ if (value is long longValue)
+ return longValue != 0;
+ if (value is ulong ulongValue)
+ return ulongValue != 0;
+ if (value is decimal decimalValue)
+ return decimalValue != 0;
+
+ return (bool) value;
}
public override byte GetByte(int ordinal)
{
- return Convert.ToByte(_wrappedDataReader.GetValue(ordinal));
+ var value = GetValue(ordinal);
+ if (value is byte byteValue)
+ return byteValue;
+
+ if (value is sbyte sbyteValue)
+ return checked((byte) sbyteValue);
+ if (value is short shortValue)
+ return checked((byte) shortValue);
+ if (value is ushort ushortValue)
+ return checked((byte) ushortValue);
+ if (value is int intValue)
+ return checked((byte) intValue);
+ if (value is uint uintValue)
+ return checked((byte) uintValue);
+ if (value is long longValue)
+ return checked((byte) longValue);
+ if (value is ulong ulongValue)
+ return checked((byte) ulongValue);
+ if (value is decimal decimalValue)
+ return (byte) decimalValue;
+ return (byte) value;
}
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length)
- {
- return _wrappedDataReader.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
- }
+ => _wrappedDataReader.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
public override char GetChar(int ordinal)
- {
- return _wrappedDataReader.GetChar(ordinal);
- }
+ => _wrappedDataReader.GetChar(ordinal);
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length)
- {
- return _wrappedDataReader.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
- }
+ => _wrappedDataReader.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
public override string GetDataTypeName(int ordinal)
- {
- return _wrappedDataReader.GetDataTypeName(ordinal);
- }
+ => _wrappedDataReader.GetDataTypeName(ordinal);
public override DateTime GetDateTime(int ordinal)
- {
- return _wrappedDataReader.GetDateTime(ordinal);
- }
+ => _wrappedDataReader.GetDateTime(ordinal);
public virtual TimeSpan GetTimeSpan(int ordinal)
- {
- TimeSpan timeSpan = GetDateTime(ordinal) - JetConfiguration.TimeSpanOffset;
-
- return timeSpan;
- }
+ => GetDateTime(ordinal) - JetConfiguration.TimeSpanOffset;
public virtual DateTimeOffset GetDateTimeOffset(int ordinal)
{
@@ -135,7 +150,7 @@ public override float GetFloat(int ordinal)
public override Guid GetGuid(int ordinal)
{
// Fix for discussion https://jetentityframeworkprovider.codeplex.com/discussions/647028
- object value = _wrappedDataReader.GetValue(ordinal);
+ var value = _wrappedDataReader.GetValue(ordinal);
if (value is byte[])
return new Guid((byte[]) value);
else
@@ -150,11 +165,11 @@ public override short GetInt16(int ordinal)
public override int GetInt32(int ordinal)
{
// Fix for discussion https://jetentityframeworkprovider.codeplex.com/discussions/647028
- object value = _wrappedDataReader.GetValue(ordinal);
+ var value = _wrappedDataReader.GetValue(ordinal);
if (value is string)
{
- byte[] buffer = Encoding.Unicode.GetBytes((string) value);
- int intValue = BitConverter.ToInt32(buffer, 0);
+ var buffer = Encoding.Unicode.GetBytes((string) value);
+ var intValue = BitConverter.ToInt32(buffer, 0);
return intValue;
}
else
@@ -207,14 +222,10 @@ public override int GetValues(object[] values)
}
public override bool HasRows
- {
- get { return _wrappedDataReader.HasRows; }
- }
+ => _wrappedDataReader.HasRows;
public override bool IsClosed
- {
- get { return _wrappedDataReader.IsClosed; }
- }
+ => _wrappedDataReader.IsClosed;
public override bool IsDBNull(int ordinal)
{
@@ -240,18 +251,12 @@ public override bool Read()
}
public override int RecordsAffected
- {
- get { return _wrappedDataReader.RecordsAffected; }
- }
+ => _wrappedDataReader.RecordsAffected;
public override object this[string name]
- {
- get { return _wrappedDataReader[name]; }
- }
+ => _wrappedDataReader[name];
public override object this[int ordinal]
- {
- get { return _wrappedDataReader[ordinal]; }
- }
+ => _wrappedDataReader[ordinal];
}
}
\ No newline at end of file
diff --git a/src/System.Data.Jet/JetStoreSchemaDefinition/JetRenameHandling.cs b/src/System.Data.Jet/JetStoreSchemaDefinition/JetRenameHandling.cs
index a62b8c3c..2e76a718 100644
--- a/src/System.Data.Jet/JetStoreSchemaDefinition/JetRenameHandling.cs
+++ b/src/System.Data.Jet/JetStoreSchemaDefinition/JetRenameHandling.cs
@@ -2,55 +2,47 @@
namespace System.Data.Jet.JetStoreSchemaDefinition
{
- class JetRenameHandling
+ internal class JetRenameHandling
{
- private static Regex _renameTableRegex = new Regex(
- $@"^\s*rename\s+table\s+{GetQuotedOrUnquotedNamePattern("tableName")}\s+to\s+{GetQuotedOrUnquotedNamePattern("newTableName")}\s*$",
+ private static readonly Regex _renameTableRegex = new Regex(
+ $@"^\s*rename\s+table\s+{GetIdentifierPattern("OldTableName")}\s+to\s+{GetIdentifierPattern("NewTableName")}\s*$",
RegexOptions.IgnoreCase);
- private static Regex _renameTableColumnRegex = new Regex(
- $@"^\s*rename\s+column\s+{GetQuotedOrUnquotedNamePattern("tableName")}\.{GetQuotedOrUnquotedNamePattern("columnName")}\s+to\s+{GetQuotedOrUnquotedNamePattern("newColumnName")}\s*$",
+ private static readonly Regex _renameTableColumnRegex = new Regex(
+ $@"^\s*rename\s+column\s+{GetIdentifierPattern("TableName")}\s*\.\s*{GetIdentifierPattern("OldColumnName")}\s+to\s+{GetIdentifierPattern("NewColumnName")}\s*$",
RegexOptions.IgnoreCase);
public static bool TryDatabaseOperation(string connectionString, string commandText)
{
- Match match;
-
-
- match = _renameTableRegex.Match(commandText);
+ var match = _renameTableRegex.Match(commandText);
if (match.Success)
{
- string tableName = match.Groups["tableName"].Value;
- string newTableName = match.Groups["newTableName"].Value;
- AdoxWrapper.RenameTable(connectionString, RemoveBrackets(tableName), RemoveBrackets(newTableName));
+ var oldTableName = match.Groups["OldTableName"].Value;
+ var newTableName = match.Groups["NewTableName"].Value;
+
+ // TODO: Only use ADOX in an OLE DB context. Use DAO in an ODBC context.
+ AdoxWrapper.RenameTable(connectionString, oldTableName, newTableName);
+
return true;
}
match = _renameTableColumnRegex.Match(commandText);
if (match.Success)
{
- string tableName = match.Groups["tableName"].Value;
- string columnName = match.Groups["columnName"].Value;
- string newColumnName = match.Groups["newColumnName"].Value;
- AdoxWrapper.RenameColumn(connectionString, RemoveBrackets(tableName), RemoveBrackets(columnName), RemoveBrackets(newColumnName));
+ var tableName = match.Groups["TableName"].Value;
+ var oldColumnName = match.Groups["OldColumnName"].Value;
+ var newColumnName = match.Groups["NewColumnName"].Value;
+
+ // TODO: Only use ADOX in an OLE DB context. Use DAO in an ODBC context.
+ AdoxWrapper.RenameColumn(connectionString, tableName, oldColumnName, newColumnName);
+
return true;
}
return false;
}
- private static string RemoveBrackets(string name)
- {
- if (name.StartsWith("[") && name.EndsWith("]"))
- return name.Substring(1, name.Length - 2);
- else
- return name;
- }
-
-
- static string GetQuotedOrUnquotedNamePattern(string key)
- {
- return $@"((?<{key}>\S*)|\[(?<{key}>.*)\])";
- }
+ private static string GetIdentifierPattern(string key)
+ => $@"(?:`(?<{key}>.*?)`|\[(?<{key}>.*?)\]|(?<{key}>\S*))";
}
}
diff --git a/src/System.Data.Jet/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs b/src/System.Data.Jet/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs
index 48427aa8..ad6d1dea 100644
--- a/src/System.Data.Jet/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs
+++ b/src/System.Data.Jet/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs
@@ -1,4 +1,6 @@
+using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
@@ -9,13 +11,15 @@ static class JetStoreDatabaseHandling
private static readonly Regex _regExIsCreateOrDropDatabaseCommand;
private static readonly Regex _regExParseCreateDatabaseCommand;
private static readonly Regex _regExParseDropDatabaseCommand;
+ private static readonly Regex _regExIsConnectionString;
+ private static readonly Regex _regExHasProvider;
+ private static readonly Regex _regExExtractFilenameFromConnectionString;
// TODO: Remove this obsolete block for .NET 5.
private static readonly Regex _regExParseObsoleteCreateDatabaseCommand;
private static readonly Regex _regExParseObsoleteDropDatabaseCommand;
private static readonly Regex _regExParseObsoleteCreateDatabaseCommandFromConnection;
private static readonly Regex _regExParseObsoleteDropDatabaseCommandFromConnection;
- private static readonly Regex _regExExtractFilenameFromConnectionString;
static JetStoreDatabaseHandling()
{
@@ -33,30 +37,44 @@ static JetStoreDatabaseHandling()
@"^\s*drop\s+database\s+'\s*(?(?:''|[^'])*)\s*'\s*(?:;|$)",
RegexOptions.IgnoreCase);
+ // Provider=Microsoft.ACE.OLEDB.12.0
+ // Driver=Microsoft.ACE.OLEDB.12.0
+ // Data Source=Joe's Database.accdb
+ // DBQ=Joe's Database.accdb
+ _regExIsConnectionString = new Regex(
+ @"^(?:.*;)?\s*(?:provider|driver|data source|dbq)\s*=",
+ RegexOptions.IgnoreCase);
+
+ // Provider=Microsoft.ACE.OLEDB.12.0
+ // Driver=Microsoft.ACE.OLEDB.12.0
+ _regExHasProvider = new Regex(
+ @"^(?:.*;)?\s*(?:provider|driver)\s*=",
+ RegexOptions.IgnoreCase);
+
+ // Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Joe's Database.accdb;
+ _regExExtractFilenameFromConnectionString = new Regex(
+ @"^(?:.*;)?\s*(?:data source|dbq)\s*=\s*(?.*?)\s*(?:;|$)",
+ RegexOptions.IgnoreCase);
+
// CREATE DATABASE Joe's Database.accdb;
_regExParseObsoleteCreateDatabaseCommand = new Regex(
@"^\s*create\s+database\s+(?.*?)\s*(?:;|$)",
RegexOptions.IgnoreCase);
- // DROP DATABASE 'Joe's Database.accdb';
+ // DROP DATABASE Joe's Database.accdb;
_regExParseObsoleteDropDatabaseCommand = new Regex(
@"^\s*drop\s+database\s+(?.*?)\s*(?:;|$)",
RegexOptions.IgnoreCase);
- // CREATE DATABASE Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Joe's Database.accdb';
+ // CREATE DATABASE Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Joe's Database.accdb;
_regExParseObsoleteCreateDatabaseCommandFromConnection = new Regex(
@"^\s*create\s+database\s+(?(provider)\s*=\s*.*?)\s*$",
RegexOptions.IgnoreCase);
- // DROP DATABASE Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Joe's Database.accdb';
+ // DROP DATABASE Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Joe's Database.accdb;
_regExParseObsoleteDropDatabaseCommandFromConnection = new Regex(
@"^\s*drop\s+database\s+(?provider\s*=\s*.*?)\s*$",
RegexOptions.IgnoreCase);
-
- // Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Joe's Database.accdb';
- _regExExtractFilenameFromConnectionString = new Regex(
- @"(?:.*;)?\s*(?:data source|dbq)\s*=\s*(?.*?)\s*(?:;|$)",
- RegexOptions.IgnoreCase);
}
public static bool TryDatabaseOperation(JetCommand command)
@@ -161,6 +179,19 @@ public static string ExtractFileNameFromConnectionString(string connectionString
return fileName;
}
+ public static bool IsConnectionString(string connectionString)
+ => _regExIsConnectionString.IsMatch(connectionString);
+
+ public static bool IsFileName(string fileName)
+ => !string.IsNullOrWhiteSpace(fileName) &&
+ !IsConnectionString(fileName) &&
+ !fileName.ToCharArray()
+ .Intersect(Path.GetInvalidPathChars())
+ .Any();
+
+ public static bool HasProvider(string connectionString)
+ => _regExHasProvider.IsMatch(connectionString);
+
public static void DeleteFile(string fileName)
{
if (!File.Exists(fileName))
diff --git a/src/System.Data.Jet/JetStoreSchemaDefinition/JetStoreSchemaDefinitionRetrieve.cs b/src/System.Data.Jet/JetStoreSchemaDefinition/JetStoreSchemaDefinitionRetrieve.cs
index 6aa7fc50..8802f6c8 100644
--- a/src/System.Data.Jet/JetStoreSchemaDefinition/JetStoreSchemaDefinitionRetrieve.cs
+++ b/src/System.Data.Jet/JetStoreSchemaDefinition/JetStoreSchemaDefinitionRetrieve.cs
@@ -85,8 +85,8 @@ private static DbDataReader GetDbDataReaderFromComplexStatement(DbConnection con
if (showStatementPosition == -1)
continue;
- commandText = commandText.ReplaceCaseInsensitive("\\(\\s*show " + table.Name + "\\s*\\)", "[" + table.TableName + "]");
- commandText = commandText.ReplaceCaseInsensitive("show " + table.Name, "[" + table.TableName + "]");
+ commandText = commandText.ReplaceCaseInsensitive("\\(\\s*show " + table.Name + "\\s*\\)", "`" + table.TableName + "`");
+ commandText = commandText.ReplaceCaseInsensitive("show " + table.Name, "`" + table.TableName + "`");
tablesToCreate.Add(table);
}
@@ -174,13 +174,13 @@ private static string GetInsertStatement(string tableName, DataRow row)
values += ", ";
}
- columns += string.Format("[{0}]", column.ColumnName);
+ columns += string.Format("`{0}`", column.ColumnName);
object value = row[column];
values += JetSyntaxHelper.ToSqlStringSwitch(value);
}
}
- return string.Format("INSERT INTO [{0}] ({1}) VALUES ({2})", tableName, columns, values);
+ return string.Format("INSERT INTO `{0}` ({1}) VALUES ({2})", tableName, columns, values);
}
[DebuggerStepThrough]
diff --git a/src/System.Data.Jet/JetStoreSchemaDefinition/SystemTableCollection.cs b/src/System.Data.Jet/JetStoreSchemaDefinition/SystemTableCollection.cs
index 5c7f60f5..3a54e99d 100644
--- a/src/System.Data.Jet/JetStoreSchemaDefinition/SystemTableCollection.cs
+++ b/src/System.Data.Jet/JetStoreSchemaDefinition/SystemTableCollection.cs
@@ -71,11 +71,11 @@ public void Refresh()
foreach (SystemTable table in this)
{
- table.DropStatement = string.Format("DROP TABLE [{0}]", table.TableName);
- table.ClearStatement = string.Format("DELETE FROM [{0}]", table.TableName);
+ table.DropStatement = string.Format("DROP TABLE `{0}`", table.TableName);
+ table.ClearStatement = string.Format("DELETE FROM `{0}`", table.TableName);
StringBuilder createStatementStringBuilder = new StringBuilder();
- createStatementStringBuilder.AppendFormat("CREATE TABLE [{0}]\r\n", table.TableName);
+ createStatementStringBuilder.AppendFormat("CREATE TABLE `{0}`\r\n", table.TableName);
createStatementStringBuilder.Append("(");
bool first = true;
foreach (Column column in table.Columns)
@@ -85,7 +85,7 @@ public void Refresh()
else
createStatementStringBuilder.Append(",");
createStatementStringBuilder.AppendLine();
- createStatementStringBuilder.AppendFormat(" [{0}] {1}{2} {3}",
+ createStatementStringBuilder.AppendFormat(" `{0}` {1}{2} {3}",
column.Name,
column.Type,
column.MaxLength == null ? "" : string.Format("({0})", column.MaxLength),
diff --git a/src/System.Data.Jet/JetSyntaxHelper.cs b/src/System.Data.Jet/JetSyntaxHelper.cs
index 493cb987..66966658 100644
--- a/src/System.Data.Jet/JetSyntaxHelper.cs
+++ b/src/System.Data.Jet/JetSyntaxHelper.cs
@@ -15,7 +15,7 @@ public static string ReplaceCaseInsensitive(this string s, string oldValue, stri
public static string ToSqlString(string value)
{
// In Jet everything's unicode
- return "'" + value.Replace("'", "''") + "'";
+ return $"'{value.Replace("'", "''")}'";
}
public static string ToSqlString(int value)
@@ -84,9 +84,9 @@ public static string ToSqlString(Guid value)
internal static string QuoteIdentifier(string name)
{
if (string.IsNullOrWhiteSpace(name))
- throw new ArgumentNullException("name");
+ throw new ArgumentNullException(nameof(name));
- return "[" + name.Replace("]", "]]") + "]";
+ return $"`{name}`";
}
///
diff --git a/src/System.Data.Jet/System.Data.Jet.csproj b/src/System.Data.Jet/System.Data.Jet.csproj
index 472c32c6..0004cb70 100644
--- a/src/System.Data.Jet/System.Data.Jet.csproj
+++ b/src/System.Data.Jet/System.Data.Jet.csproj
@@ -39,6 +39,7 @@
+
diff --git a/test/EFCore.Jet.FunctionalTests/config.json b/test/EFCore.Jet.FunctionalTests/config.json
index f5fe4f83..6c66070b 100644
--- a/test/EFCore.Jet.FunctionalTests/config.json
+++ b/test/EFCore.Jet.FunctionalTests/config.json
@@ -1,7 +1,7 @@
{
"Test": {
"Jet": {
- "DefaultConnection": "Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=Jet.accdb;ExtendedAnsiSQL=1"
+ "DefaultConnection": "DBQ=Jet.accdb"
}
}
}