using System.Data.Common; using System.Data.Jet.JetStoreSchemaDefinition; using System.IO; using System.Linq; using System.Text.RegularExpressions; namespace System.Data.Jet { public class JetConnection : DbConnection, IDisposable, ICloneable { private ConnectionState _state; private string _connectionString; private bool _frozen; internal DbConnection InnerConnection { get; private set; } internal JetTransaction ActiveTransaction { get; set; } /// /// Initializes a new instance of the class. /// public JetConnection() : this(null, null) { } /// /// Initializes a new instance of the class. /// /// The connection string. public JetConnection(string connectionString) : this(connectionString, null) { } /// /// Initializes a new instance of the class. /// /// The underlying provider factory to use by Jet. Supported are /// `OdbcFactory` and `OleDbFactory`. public JetConnection(DbProviderFactory dataAccessProviderFactory) : this(null, dataAccessProviderFactory) { } /// /// Initializes a new instance of the class. /// /// The connection string. /// The underlying provider factory to use by Jet. Supported are /// `OdbcFactory` and `OleDbFactory`. public JetConnection(string connectionString, DbProviderFactory dataAccessProviderFactory) { ConnectionString = connectionString; if (dataAccessProviderFactory != null) DataAccessProviderFactory = dataAccessProviderFactory; _state = ConnectionState.Closed; } /// /// Gets or sets a value indicating whether this instance is empty. /// It is similar to connection to master and can be used only to create and drop databases /// /// /// true if this instance is empty; otherwise, false. /// public bool IsEmpty { get; set; } /// /// Gets the for this . /// protected override DbProviderFactory DbProviderFactory => JetFactory; /// /// Gets or sets an `OdbcFactory` or `OleDbFactory` object, to use as the underlying data /// access API. Jet uses this provider factory internally for all data access operations. /// /// This property can only be set as long as the connection is closed. public DbProviderFactory DataAccessProviderFactory { get => JetFactory?.InnerFactory; set { if (value == null) throw new ArgumentNullException(nameof(value)); if (JetFactory != null && JetFactory != value) throw new InvalidOperationException($"The {DataAccessProviderFactory} property can only be set once."); JetFactory = new JetFactory(this, value); } } /// /// Gets a `JetProviderFactory` object, that can be used to create Jet specific objects (e.g. `JetCommand`). /// public JetFactory JetFactory { get; private set; } /// /// Starts a database transaction. /// /// Specifies the isolation level for the transaction. /// /// An object representing the new transaction. /// protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) { if (State != ConnectionState.Open) throw new InvalidOperationException(Messages.CannotCallMethodInThisConnectionState("BeginDbTransaction", State)); if (ActiveTransaction != null) throw new InvalidOperationException(Messages.UnsupportedParallelTransactions()); switch (isolationLevel) { case IsolationLevel.Serializable: ActiveTransaction = CreateTransaction(IsolationLevel.ReadCommitted); break; default: ActiveTransaction = CreateTransaction(isolationLevel); break; } return ActiveTransaction; } private JetTransaction CreateTransaction(IsolationLevel isolationLevel) => new JetTransaction(this, isolationLevel); /// /// Changes the current database for an open connection. /// /// Specifies the name of the database for the connection to use. public override void ChangeDatabase(string databaseName) { if (State != ConnectionState.Open) throw new InvalidOperationException(Messages.CannotCallMethodInThisConnectionState(nameof(ConnectionString), ConnectionState.Open, State)); throw new InvalidOperationException(Messages.MethodUnsupportedByJet("ChangeDatabase")); } /// /// Closes the connection to the database. This is the preferred method of closing any open connection. /// public override void Close() { if (ActiveTransaction != null) { ActiveTransaction.Rollback(); ActiveTransaction = null; } if (InnerConnection != null) { InnerConnection.StateChange -= WrappedConnection_StateChange; InnerConnectionFactory.Instance.CloseConnection(_connectionString, InnerConnection); InnerConnection = null; } if (_state == ConnectionState.Closed) return; _state = ConnectionState.Closed; OnStateChange(new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed)); } /// /// Gets or sets the string used to open the connection. /// public override string ConnectionString { get => _connectionString; set { 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; } } /// /// Gets the time to wait while establishing a connection before terminating the attempt and generating an error. /// For Jet this time is unlimited /// public override int ConnectionTimeout => 0; /// /// Creates and returns a object associated with the current connection. /// /// /// A object. /// protected override DbCommand CreateDbCommand() { var command = JetFactory.CreateCommand(); command.Connection = this; return command; } /// /// This property is always empty in Jet. Use DataSource property instead. /// Gets the name of the current database after a connection is opened, or the database name specified /// in the connection string before the connection is opened. /// public override string Database => string.Empty; /// /// Gets the name of the file to open. /// public override string DataSource => GetDatabaseFilePath(_connectionString); /// /// Releases the unmanaged resources used by the and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { _connectionString = string.Empty; if (disposing) Close(); base.Dispose(disposing); } /// /// Enlists in the specified transaction. /// /// A reference to an existing in which to enlist. public override void EnlistTransaction(System.Transactions.Transaction transaction) { if (InnerConnection == null) throw new InvalidOperationException(Messages.PropertyNotInitialized("Connection")); InnerConnection.EnlistTransaction(transaction); } /// /// Returns schema information for the data source of this using the specified string for the schema name. /// /// Specifies the name of the schema to return. /// /// A that contains schema information. /// /// /// /// public override DataTable GetSchema(string collectionName) { if (State != ConnectionState.Open) throw new InvalidOperationException(Messages.CannotCallMethodInThisConnectionState("GetSchema", State)); return InnerConnection.GetSchema(collectionName); } /// /// Returns schema information for the data source of this . /// /// /// A that contains schema information. /// /// /// /// public override DataTable GetSchema() { if (State != ConnectionState.Open) throw new InvalidOperationException(Messages.CannotCallMethodInThisConnectionState("GetSchema", State)); return InnerConnection.GetSchema(); } /// /// Returns schema information for the data source of this using the specified string for the schema name and the specified string array for the restriction values. /// /// Specifies the name of the schema to return. /// Specifies a set of restriction values for the requested schema. /// /// A that contains schema information. /// /// /// /// public override DataTable GetSchema(string collectionName, string[] restrictionValues) { if (State != ConnectionState.Open) throw new InvalidOperationException(Messages.CannotCallMethodInThisConnectionState("GetSchema", State)); return InnerConnection.GetSchema(collectionName, restrictionValues); } /// /// Opens a database connection with the settings specified by the . /// public override void Open() { if (IsEmpty) return; if (string.IsNullOrWhiteSpace(_connectionString)) throw new InvalidOperationException(Messages.PropertyNotInitialized(nameof(ConnectionString))); 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( ExpandDatabaseFilePath(_connectionString), JetFactory.InnerFactory); InnerConnection.StateChange += WrappedConnection_StateChange; _state = ConnectionState.Open; OnStateChange(new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open)); } catch { Close(); throw; } } /// /// Gets a string that represents the version of the server to which the object is connected. /// public override string ServerVersion { get { if (State != ConnectionState.Open) throw new InvalidOperationException(Messages.CannotReadPropertyValueInThisConnectionState(nameof(ServerVersion), State)); return InnerConnection.ServerVersion; } } /// /// Gets a string that describes the state of the connection. /// public override ConnectionState State { get { return _state; } } void WrappedConnection_StateChange(object sender, StateChangeEventArgs e) { OnStateChange(e); } public bool TableExists(string tableName) { var oldConnectionState = State; bool tableExists; if (oldConnectionState == ConnectionState.Closed) Open(); try { var sqlFormat = "select count(*) from [{0}] where 1=2"; CreateCommand(string.Format(sqlFormat, tableName)) .ExecuteNonQuery(); tableExists = true; } catch { tableExists = false; } if (oldConnectionState == ConnectionState.Closed) Close(); return tableExists; } public DbCommand CreateCommand(string commandText, int? commandTimeout = null) { if (JetFactory == null) throw new InvalidOperationException(Messages.PropertyNotInitialized(nameof(DataAccessProviderFactory))); if (string.IsNullOrEmpty(commandText)) // SqlCommand will complain if the command text is empty commandText = Environment.NewLine; var command = (JetCommand) JetFactory.CreateCommand(); command.CommandText = commandText; if (commandTimeout.HasValue) command.CommandTimeout = commandTimeout.Value; return command; } /// /// Creates a new object that is a copy of the current instance. /// /// /// A new object that is a copy of this instance. /// object ICloneable.Clone() { var clone = new JetConnection(); if (InnerConnection != null) clone.InnerConnection = InnerConnectionFactory.Instance.OpenConnection(_connectionString, JetFactory.InnerFactory); return clone; } public void Freeze() { if (!string.IsNullOrWhiteSpace(ConnectionString)) _frozen = true; } /// /// Clears the pool. /// /// The connection. public static void ClearPool(JetConnection connection) { // Actually Jet does not support pools } /// /// Clears all pools. /// public static void ClearAllPools() => InnerConnectionFactory.Instance.ClearAllPools(); public void CreateEmptyDatabase() => CreateEmptyDatabase(DataSource, DataAccessProviderFactory); public static string CreateEmptyDatabase(string fileName, DbProviderFactory dataAccessProviderFactory) => AdoxWrapper.CreateEmptyDatabase(fileName, 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, 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; } public void DropDatabase() => DropDatabase(_connectionString); public static void DropDatabase(string fileNameOrConnectionString) { var fileName = JetStoreDatabaseHandling.ExtractFileNameFromConnectionString(fileNameOrConnectionString); if (string.IsNullOrWhiteSpace(fileName)) throw new InvalidOperationException("The file name or connection string is invalid."); JetStoreDatabaseHandling.DeleteFile(fileName); } public bool DatabaseExists() => DatabaseExists(_connectionString); public static bool DatabaseExists(string fileNameOrConnectionString) { var fileName = JetStoreDatabaseHandling.ExtractFileNameFromConnectionString(fileNameOrConnectionString) .Trim('"'); if (string.IsNullOrWhiteSpace(fileName)) throw new InvalidOperationException("The file name or connection string is invalid."); return File.Exists(fileName); } 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); 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 : DataAccessProviderType.Odbc; } public static DataAccessProviderType GetDataAccessProviderType(DbProviderFactory providerFactory) { var isOleDb = providerFactory .GetType() .GetTypesInHierarchy() .FirstOrDefault( t => string.Equals( t.FullName, "System.Data.OleDb.OleDbFactory", StringComparison.OrdinalIgnoreCase)) != null; var isOdbc = providerFactory .GetType() .GetTypesInHierarchy() .FirstOrDefault( t => string.Equals( t.FullName, "System.Data.Odbc.OdbcFactory", StringComparison.OrdinalIgnoreCase)) != null; if (isOdbc && isOleDb) throw new InvalidOperationException(); if (!isOdbc && !isOleDb) throw new ArgumentException($"The parameter is neither of type OdbcFactory nor OleDbFactory.", nameof(providerFactory)); return isOleDb ? DataAccessProviderType.OleDb : DataAccessProviderType.Odbc; } } }