//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft // Microsoft //------------------------------------------------------------------------------ namespace System.Data.OleDb { using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.ProviderBase; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using SysES = System.EnterpriseServices; using SysTx = System.Transactions; sealed internal class OleDbConnectionInternal : DbConnectionInternal, IDisposable { static private volatile OleDbServicesWrapper idataInitialize; static private object dataInitializeLock = new object(); internal readonly OleDbConnectionString ConnectionString; // parsed connection string attributes // A SafeHandle is used instead of a RCW because we need to fake the CLR into not marshalling // OLE DB Services is marked apartment thread, but it actually supports/requires free-threading. // However the CLR doesn't know this and attempts to marshal the interfaces back to their original context. // But the OLE DB doesn't marshal very well if at all. Our workaround is based on the fact // OLE DB is free-threaded and allows the workaround. // Creating DataSource/Session would requiring marshalling DataLins to its original context // and has a severe performance impact (when working with transactions), hence our workaround to not Marshal. // Creating a Command would requiring marshalling Session to its original context and // actually doesn't work correctly, without our workaround you must execute the command in // the same context of the connection open. This doesn't work for pooled objects that contain // an open OleDbConnection. // We don't do extra work at this time to allow the DataReader to be used in a different context // from which the command was executed in. See WebData 64320, IRowset.GetNextRows will throw InvalidCastException // In V1.0, we worked around the performance impact of creating a DataSource/Session using // WrapIUnknownWithComObject which creates a new RCW without searching for existing RCW // effectively faking out the CLR into thinking the call is in the correct context. // We also would use Marshal.ReleaseComObject to force the release of the 'temporary' RCW. // In V1.1, we worked around the CreateCommand issue with the same WrapIUnknownWithComObject trick. // In V2.0, the performance of using WrapIUnknownWithComObject & ReleaseComObject severly degraded. // Using a SafeHandle (for lifetime control) and a delegate to call the apporiate COM method // offered much better performance. // the "Data Source object". private readonly DataSourceWrapper _datasrcwrp; // the "Session object". private readonly SessionWrapper _sessionwrp; private WeakReference weakTransaction; // When set to true the current connection is enlisted in a transaction that must be // un-enlisted during Deactivate. private bool _unEnlistDuringDeactivate; internal OleDbConnectionInternal(OleDbConnectionString constr, OleDbConnection connection) : base () { #if DEBUG try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath if (null != connection) { connection.UserConnectionOptions.DemandPermission(); } else { constr.DemandPermission(); } } catch(System.Security.SecurityException) { System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath"); throw; } #endif Debug.Assert((null != constr) && !constr.IsEmpty, "empty connectionstring"); ConnectionString = constr; if (constr.PossiblePrompt && !System.Environment.UserInteractive) { throw ODB.PossiblePromptNotUserInteractive(); } try { // this is the native DataLinks object which pools the native datasource/session OleDbServicesWrapper wrapper = OleDbConnectionInternal.GetObjectPool(); _datasrcwrp = new DataSourceWrapper(); // DataLinks wrapper will call IDataInitialize::GetDataSource to create the DataSource // uses constr.ActualConnectionString, no InfoMessageEvent checking wrapper.GetDataSource(constr, ref _datasrcwrp); Debug.Assert(!_datasrcwrp.IsInvalid, "bad DataSource"); // initialization is delayed because of OleDbConnectionStringBuilder only wants // pre-Initialize IDBPropertyInfo & IDBProperties on the data source if (null != connection) { _sessionwrp = new SessionWrapper(); // From the DataSource object, will call IDBInitialize.Initialize & IDBCreateSession.CreateSession // We always need both called so we use a single call for a single DangerousAddRef/DangerousRelease pair. OleDbHResult hr = _datasrcwrp.InitializeAndCreateSession(constr, ref _sessionwrp); // process the HResult here instead of from the SafeHandle because the possibility // of an InfoMessageEvent. if ((0 <= hr) && !_sessionwrp.IsInvalid) { // process infonessage events OleDbConnection.ProcessResults(hr, connection, connection); } else { Exception e = OleDbConnection.ProcessResults(hr, null, null); Debug.Assert(null != e, "CreateSessionError"); throw e; } Debug.Assert(!_sessionwrp.IsInvalid, "bad Session"); } } catch { if (null != _sessionwrp) { _sessionwrp.Dispose(); _sessionwrp = null; } if (null != _datasrcwrp) { _datasrcwrp.Dispose(); _datasrcwrp = null; } throw; } } internal OleDbConnection Connection { get { return (OleDbConnection)Owner; } } internal bool HasSession { get { return (null != _sessionwrp); } } internal OleDbTransaction LocalTransaction { get { OleDbTransaction result = null; if (null != weakTransaction) { result = ((OleDbTransaction)weakTransaction.Target); } return result; } set { weakTransaction = null; if (null != value) { weakTransaction = new WeakReference((OleDbTransaction)value); } } } private string Provider { get { return ConnectionString.Provider; } } override public string ServerVersion { // MDAC 55481 // consider making a method, not a property get { object value = GetDataSourceValue(OleDbPropertySetGuid.DataSourceInfo, ODB.DBPROP_DBMSVER); return Convert.ToString(value, CultureInfo.InvariantCulture); } } // grouping the native OLE DB casts togther by required interfaces and optional interfaces, connection then session // want these to be methods, not properties otherwise they appear in VS7 managed debugger which attempts to evaluate them // required interface, safe cast internal IDBPropertiesWrapper IDBProperties() { Debug.Assert(null != _datasrcwrp, "IDBProperties: null datasource"); return _datasrcwrp.IDBProperties(this); } // required interface, safe cast internal IOpenRowsetWrapper IOpenRowset() { Debug.Assert(null != _datasrcwrp, "IOpenRowset: null datasource"); Debug.Assert(null != _sessionwrp, "IOpenRowset: null session"); return _sessionwrp.IOpenRowset(this); } // optional interface, unsafe cast private IDBInfoWrapper IDBInfo() { Debug.Assert(null != _datasrcwrp, "IDBInfo: null datasource"); return _datasrcwrp.IDBInfo(this); } // optional interface, unsafe cast internal IDBSchemaRowsetWrapper IDBSchemaRowset() { Debug.Assert(null != _datasrcwrp, "IDBSchemaRowset: null datasource"); Debug.Assert(null != _sessionwrp, "IDBSchemaRowset: null session"); return _sessionwrp.IDBSchemaRowset(this); } // optional interface, unsafe cast internal ITransactionJoinWrapper ITransactionJoin() { Debug.Assert(null != _datasrcwrp, "ITransactionJoin: null datasource"); Debug.Assert(null != _sessionwrp, "ITransactionJoin: null session"); return _sessionwrp.ITransactionJoin(this); } // optional interface, unsafe cast internal UnsafeNativeMethods.ICommandText ICommandText() { Debug.Assert(null != _datasrcwrp, "IDBCreateCommand: null datasource"); Debug.Assert(null != _sessionwrp, "IDBCreateCommand: null session"); object icommandText = null; OleDbHResult hr = _sessionwrp.CreateCommand(ref icommandText); Debug.Assert((0 <= hr) || (null == icommandText), "CreateICommandText: error with ICommandText"); if (hr < 0) { if (OleDbHResult.E_NOINTERFACE != hr) { // MDAC 57856 ProcessResults(hr); } else { SafeNativeMethods.Wrapper.ClearErrorInfo(); } } return (UnsafeNativeMethods.ICommandText)icommandText; } override protected void Activate(SysTx.Transaction transaction) { throw ADP.NotSupported(); } override public DbTransaction BeginTransaction(IsolationLevel isolationLevel) { OleDbConnection.ExecutePermission.Demand(); OleDbConnection outerConnection = Connection; if (null != LocalTransaction) { throw ADP.ParallelTransactionsNotSupported(outerConnection); } object unknown = null; OleDbTransaction transaction; try { transaction = new OleDbTransaction(outerConnection, null, isolationLevel); Bid.Trace(" %d#, ITransactionLocal\n", ObjectID); Debug.Assert(null != _datasrcwrp, "ITransactionLocal: null datasource"); Debug.Assert(null != _sessionwrp, "ITransactionLocal: null session"); unknown = _sessionwrp.ComWrapper(); UnsafeNativeMethods.ITransactionLocal value = (unknown as UnsafeNativeMethods.ITransactionLocal); if (null == value) { throw ODB.TransactionsNotSupported(Provider, (Exception)null); } transaction.BeginInternal(value); } finally { if (null != unknown) { Marshal.ReleaseComObject(unknown); } } LocalTransaction = transaction; return transaction; } override protected DbReferenceCollection CreateReferenceCollection() { return new OleDbReferenceCollection(); } override protected void Deactivate() { // used by both managed and native pooling NotifyWeakReference(OleDbReferenceCollection.Closing); if (_unEnlistDuringDeactivate) { // Un-enlist transaction as OLEDB connection pool is unaware of managed transactions. EnlistTransactionInternal(null); } OleDbTransaction transaction = LocalTransaction; if (null != transaction) { LocalTransaction = null; // required to rollback any transactions on this connection // before releasing the back to the oledb connection pool transaction.Dispose(); } } public override void Dispose() { // MDAC 65459 Debug.Assert(null == LocalTransaction, "why was Deactivate not called first"); if (null != _sessionwrp) { _sessionwrp.Dispose(); } if (null != _datasrcwrp) { _datasrcwrp.Dispose(); } base.Dispose(); } override public void EnlistTransaction(SysTx.Transaction transaction) { // MDAC 78997 OleDbConnection.VerifyExecutePermission(); OleDbConnection outerConnection = Connection; if (null != LocalTransaction) { throw ADP.LocalTransactionPresent(); } EnlistTransactionInternal(transaction); } internal void EnlistTransactionInternal(SysTx.Transaction transaction) { OleDbConnection.VerifyExecutePermission(); SysTx.IDtcTransaction oleTxTransaction = ADP.GetOletxTransaction(transaction); IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#\n", ObjectID); try { using(ITransactionJoinWrapper transactionJoin = ITransactionJoin()) { if (null == transactionJoin.Value) { throw ODB.TransactionsNotSupported(Provider, (Exception)null); } transactionJoin.Value.JoinTransaction(oleTxTransaction, (int) IsolationLevel.Unspecified, 0, IntPtr.Zero); _unEnlistDuringDeactivate = (null != transaction); } } finally { Bid.ScopeLeave(ref hscp); } EnlistedTransaction = transaction; } internal object GetDataSourceValue(Guid propertySet, int propertyID) { object value = GetDataSourcePropertyValue(propertySet, propertyID); if ((value is OleDbPropertyStatus) || Convert.IsDBNull(value)) { value = null; } return value; } internal object GetDataSourcePropertyValue(Guid propertySet, int propertyID) { OleDbHResult hr; tagDBPROP[] dbprops; using(IDBPropertiesWrapper idbProperties = IDBProperties()) { using(PropertyIDSet propidset = new PropertyIDSet(propertySet, propertyID)) { using(DBPropSet propset = new DBPropSet(idbProperties.Value, propidset, out hr)) { if (hr < 0) { // VSDD 621427: OLEDB Data Reader masks provider specific errors by raising "Internal .Net Framework Data Provider error 30." // DBPropSet c-tor will register the exception and it will be raised at GetPropertySet call in case of failure SafeNativeMethods.Wrapper.ClearErrorInfo(); } dbprops = propset.GetPropertySet(0, out propertySet); } } } if (OleDbPropertyStatus.Ok == dbprops[0].dwStatus) { return dbprops[0].vValue; } return dbprops[0].dwStatus; } internal DataTable BuildInfoLiterals() { using(IDBInfoWrapper wrapper = IDBInfo()) { UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value; if (null == dbInfo) { return null; } DataTable table = new DataTable("DbInfoLiterals"); table.Locale = CultureInfo.InvariantCulture; DataColumn literalName = new DataColumn("LiteralName", typeof(String)); DataColumn literalValue = new DataColumn("LiteralValue", typeof(String)); DataColumn invalidChars = new DataColumn("InvalidChars", typeof(String)); DataColumn invalidStart = new DataColumn("InvalidStartingChars", typeof(String)); DataColumn literal = new DataColumn("Literal", typeof(Int32)); DataColumn maxlen = new DataColumn("Maxlen", typeof(Int32)); table.Columns.Add(literalName); table.Columns.Add(literalValue); table.Columns.Add(invalidChars); table.Columns.Add(invalidStart); table.Columns.Add(literal); table.Columns.Add(maxlen); OleDbHResult hr; int literalCount = 0; IntPtr literalInfo = ADP.PtrZero; using(DualCoTaskMem handle = new DualCoTaskMem(dbInfo, null, out literalCount, out literalInfo, out hr)) { // All literals were either invalid or unsupported. The provider allocates memory for *prgLiteralInfo and sets the value of the fSupported element in all of the structures to FALSE. The consumer frees this memory when it no longer needs the information. if (OleDbHResult.DB_E_ERRORSOCCURRED != hr) { long offset = literalInfo.ToInt64(); tagDBLITERALINFO tag = new tagDBLITERALINFO(); for (int i = 0; i < literalCount; ++i, offset += ODB.SizeOf_tagDBLITERALINFO) { Marshal.PtrToStructure((IntPtr)offset, tag); DataRow row = table.NewRow(); row[literalName ] = ((OleDbLiteral) tag.it).ToString(); row[literalValue] = tag.pwszLiteralValue; row[invalidChars] = tag.pwszInvalidChars; row[invalidStart] = tag.pwszInvalidStartingChars; row[literal ] = tag.it; row[maxlen ] = tag.cchMaxLen; table.Rows.Add(row); row.AcceptChanges(); } if (hr < 0) { // ignore infomsg ProcessResults(hr); } } else { SafeNativeMethods.Wrapper.ClearErrorInfo(); } } return table; } } internal DataTable BuildInfoKeywords() { DataTable table = new DataTable(ODB.DbInfoKeywords); table.Locale = CultureInfo.InvariantCulture; DataColumn keyword = new DataColumn(ODB.Keyword, typeof(String)); table.Columns.Add(keyword); if(!AddInfoKeywordsToTable(table,keyword)){ table = null; } return table; } internal bool AddInfoKeywordsToTable(DataTable table, DataColumn keyword) { using(IDBInfoWrapper wrapper = IDBInfo()) { UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value; if (null == dbInfo) { return false; } OleDbHResult hr; string keywords; Bid.Trace(" %d#\n", ObjectID); hr = dbInfo.GetKeywords(out keywords); Bid.Trace(" %08X{HRESULT}\n", hr); if (hr < 0) { // ignore infomsg ProcessResults(hr); } if (null != keywords) { string[] values = keywords.Split(new char[1] { ',' }); for (int i = 0; i < values.Length; ++i) { DataRow row = table.NewRow(); row[keyword] = values[i]; table.Rows.Add(row); row.AcceptChanges(); } } return true; } } internal DataTable BuildSchemaGuids() { DataTable table = new DataTable(ODB.SchemaGuids); table.Locale = CultureInfo.InvariantCulture; DataColumn schemaGuid = new DataColumn(ODB.Schema, typeof(Guid)); DataColumn restrictionSupport = new DataColumn(ODB.RestrictionSupport, typeof(Int32)); table.Columns.Add(schemaGuid); table.Columns.Add(restrictionSupport); SchemaSupport[] supportedSchemas = GetSchemaRowsetInformation(); if (null != supportedSchemas) { object[] values = new object[2]; table.BeginLoadData(); for (int i = 0; i < supportedSchemas.Length; ++i) { values[0] = supportedSchemas[i]._schemaRowset; values[1] = supportedSchemas[i]._restrictions; table.LoadDataRow(values, LoadOption.OverwriteChanges); } table.EndLoadData(); } return table; } internal string GetLiteralInfo(int literal) { using(IDBInfoWrapper wrapper = IDBInfo()) { UnsafeNativeMethods.IDBInfo dbInfo = wrapper.Value; if (null == dbInfo) { return null; } string literalValue = null; IntPtr literalInfo = ADP.PtrZero; int literalCount = 0; OleDbHResult hr; using(DualCoTaskMem handle = new DualCoTaskMem(dbInfo, new int[1] { literal }, out literalCount, out literalInfo, out hr)) { // All literals were either invalid or unsupported. The provider allocates memory for *prgLiteralInfo and sets the value of the fSupported element in all of the structures to FALSE. The consumer frees this memory when it no longer needs the information. if (OleDbHResult.DB_E_ERRORSOCCURRED != hr) { if ((1 == literalCount) && Marshal.ReadInt32(literalInfo, ODB.OffsetOf_tagDBLITERALINFO_it) == literal) { // WebData 98612 literalValue = Marshal.PtrToStringUni(Marshal.ReadIntPtr(literalInfo, 0)); } if (hr < 0) { // ignore infomsg ProcessResults(hr); } } else { SafeNativeMethods.Wrapper.ClearErrorInfo(); } } return literalValue; } } internal SchemaSupport[] GetSchemaRowsetInformation() { OleDbConnectionString constr = ConnectionString; SchemaSupport[] supportedSchemas = constr.SchemaSupport; if (null != supportedSchemas) { return supportedSchemas; } using(IDBSchemaRowsetWrapper wrapper = IDBSchemaRowset()) { UnsafeNativeMethods.IDBSchemaRowset dbSchemaRowset = wrapper.Value; if (null == dbSchemaRowset) { return null; // IDBSchemaRowset not supported } OleDbHResult hr; int schemaCount = 0; IntPtr schemaGuids = ADP.PtrZero; IntPtr schemaRestrictions = ADP.PtrZero; using(DualCoTaskMem safehandle = new DualCoTaskMem(dbSchemaRowset, out schemaCount, out schemaGuids, out schemaRestrictions, out hr)) { dbSchemaRowset = null; if (hr < 0) { // ignore infomsg ProcessResults(hr); } supportedSchemas = new SchemaSupport[schemaCount]; if (ADP.PtrZero != schemaGuids) { for (int i = 0, offset = 0; i < supportedSchemas.Length; ++i, offset += ODB.SizeOf_Guid) { IntPtr ptr = ADP.IntPtrOffset(schemaGuids, i * ODB.SizeOf_Guid); supportedSchemas[i]._schemaRowset = (Guid) Marshal.PtrToStructure(ptr, typeof(Guid)); } } if (ADP.PtrZero != schemaRestrictions) { for (int i = 0; i < supportedSchemas.Length; ++i) { supportedSchemas[i]._restrictions = Marshal.ReadInt32(schemaRestrictions, i * 4); } } } constr.SchemaSupport = supportedSchemas; return supportedSchemas; } } internal DataTable GetSchemaRowset(Guid schema, object[] restrictions) { IntPtr hscp; Bid.ScopeEnter(out hscp, " %d#, schema=%ls, restrictions\n", ObjectID, schema); try { if (null == restrictions) { // MDAC 62243 restrictions = new object[0]; } DataTable dataTable = null; using(IDBSchemaRowsetWrapper wrapper = IDBSchemaRowset()) { UnsafeNativeMethods.IDBSchemaRowset dbSchemaRowset = wrapper.Value; if (null == dbSchemaRowset) { throw ODB.SchemaRowsetsNotSupported(Provider); } UnsafeNativeMethods.IRowset rowset = null; OleDbHResult hr; Bid.Trace(" %d#\n", ObjectID); hr = dbSchemaRowset.GetRowset(ADP.PtrZero, ref schema, restrictions.Length, restrictions, ref ODB.IID_IRowset, 0, ADP.PtrZero, out rowset); Bid.Trace(" %08X{HRESULT}\n", hr); if (hr < 0) { // ignore infomsg ProcessResults(hr); } if (null != rowset) { using(OleDbDataReader dataReader = new OleDbDataReader(Connection, null, 0, CommandBehavior.Default)) { dataReader.InitializeIRowset(rowset, ChapterHandle.DB_NULL_HCHAPTER, IntPtr.Zero); dataReader.BuildMetaInfo(); dataReader.HasRowsRead(); dataTable = new DataTable(); dataTable.Locale = CultureInfo.InvariantCulture; dataTable.TableName = OleDbSchemaGuid.GetTextFromValue(schema); OleDbDataAdapter.FillDataTable(dataReader, dataTable); } } return dataTable; } } finally { Bid.ScopeLeave(ref hscp); } } // returns true if there is an active data reader on the specified command internal bool HasLiveReader(OleDbCommand cmd) { OleDbDataReader reader = null; if (null != ReferenceCollection) { reader = ReferenceCollection.FindItem(OleDbReferenceCollection.DataReaderTag, (dataReader) => cmd == dataReader.Command); } return (reader != null); } private void ProcessResults(OleDbHResult hr) { OleDbConnection connection = Connection; // get value from weakref only once Exception e = OleDbConnection.ProcessResults(hr, connection, connection); if (null != e) { throw e; } } internal bool SupportSchemaRowset(Guid schema) { SchemaSupport[] schemaSupport = GetSchemaRowsetInformation(); if (null != schemaSupport) { // MDAC 68385 for (int i = 0; i < schemaSupport.Length; ++i) { if (schema == schemaSupport[i]._schemaRowset) { return true; } } } return false; } [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] static private object CreateInstanceDataLinks() { Type datalink = Type.GetTypeFromCLSID(ODB.CLSID_DataLinks, true); return Activator.CreateInstance(datalink, System.Reflection.BindingFlags.Public|System.Reflection.BindingFlags.Instance, null, null, CultureInfo.InvariantCulture, null); } // @devnote: should be multithread safe access to OleDbConnection.idataInitialize, // though last one wins for setting variable. It may be different objects, but // OLE DB will ensure I'll work with just the single pool static private OleDbServicesWrapper GetObjectPool() { OleDbServicesWrapper wrapper = OleDbConnectionInternal.idataInitialize; if (null == wrapper) { lock(dataInitializeLock) { wrapper = OleDbConnectionInternal.idataInitialize; if (null == wrapper) { VersionCheck(); object datalinks; try { datalinks = CreateInstanceDataLinks(); } catch (Exception e) { // if (!ADP.IsCatchableExceptionType(e)) { throw; } throw ODB.MDACNotAvailable(e); } if (null == datalinks) { throw ODB.MDACNotAvailable(null); } wrapper = new OleDbServicesWrapper(datalinks); OleDbConnectionInternal.idataInitialize = wrapper; } } } Debug.Assert(null != wrapper, "GetObjectPool: null dataInitialize"); return wrapper; } static private void VersionCheck() { // $ if (ApartmentState.Unknown == Thread.CurrentThread.GetApartmentState()) { SetMTAApartmentState(); } ADP.CheckVersionMDAC(false); } [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] static private void SetMTAApartmentState() { // we are defaulting to a multithread apartment state Thread.CurrentThread.SetApartmentState(ApartmentState.MTA); } // @devnote: should be multithread safe static public void ReleaseObjectPool() { OleDbConnectionInternal.idataInitialize = null; } internal OleDbTransaction ValidateTransaction(OleDbTransaction transaction, string method) { if (null != this.weakTransaction) { OleDbTransaction head = (OleDbTransaction) this.weakTransaction.Target; if ((null != head) && this.weakTransaction.IsAlive) { head = OleDbTransaction.TransactionUpdate(head); // either we are wrong or finalize was called and object still alive Debug.Assert(null != head, "unexcpted Transaction state"); } // else transaction has finalized on user if (null != head) { if (null == transaction) { // valid transaction exists and cmd doesn't have it throw ADP.TransactionRequired(method); } else { OleDbTransaction tail = OleDbTransaction.TransactionLast(head); if (tail != transaction) { if (tail.Connection != transaction.Connection) { throw ADP.TransactionConnectionMismatch(); } // else cmd has incorrect transaction throw ADP.TransactionCompleted(); } // else cmd has correct transaction return transaction; } } else { // cleanup for Finalized transaction this.weakTransaction = null; } } else if ((null != transaction) && (null != transaction.Connection)) { // MDAC 72706 throw ADP.TransactionConnectionMismatch(); } // else no transaction and cmd is correct // MDAC 61649 // if transactionObject is from this connection but zombied // and no transactions currently exists - then ignore the bogus object return null; } internal Dictionary GetPropertyInfo(Guid[] propertySets) { bool isopen = HasSession; OleDbConnectionString constr = ConnectionString; Dictionary properties = null; if (null == propertySets) { propertySets = new Guid[0]; } using(PropertyIDSet propidset = new PropertyIDSet(propertySets)) { using(IDBPropertiesWrapper idbProperties = IDBProperties()) { using(PropertyInfoSet infoset = new PropertyInfoSet(idbProperties.Value, propidset)) { properties = infoset.GetValues(); } } } return properties; } } }