//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // [....] // [....] //------------------------------------------------------------------------------ using System; using System.ComponentModel; //Component using System.Data; using System.Data.Common; using System.Data.ProviderBase; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; // todo: // There may be two ways to improve performance: // 1. pool statements on the connection object // 2. Do not create a datareader object for non-datareader returning command execution. // // We do not want to do the effort unless we have to squeze performance. namespace System.Data.Odbc { [ DefaultEvent("RecordsAffected"), ToolboxItem(true), Designer("Microsoft.VSDesigner.Data.VS.OdbcCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner) ] public sealed class OdbcCommand : DbCommand, ICloneable { private static int _objectTypeCount; // Bid counter internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); private string _commandText; private CommandType _commandType; private int _commandTimeout = ADP.DefaultCommandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; private bool _isPrepared; // true if the command is prepared private OdbcConnection _connection; private OdbcTransaction _transaction; private WeakReference weakDataReaderReference; private CMDWrapper _cmdWrapper; private OdbcParameterCollection _parameterCollection; // Parameter collection private ConnectionState cmdState; public OdbcCommand() : base() { GC.SuppressFinalize(this); } public OdbcCommand(string cmdText) : this() { // note: arguments are assigned to properties so we do not have to trace them. // We still need to include them into the argument list of the definition! CommandText = cmdText; } public OdbcCommand(string cmdText, OdbcConnection connection) : this() { CommandText = cmdText; Connection = connection; } public OdbcCommand(string cmdText, OdbcConnection connection, OdbcTransaction transaction) : this() { CommandText = cmdText; Connection = connection; Transaction = transaction; } private void DisposeDeadDataReader() { if (ConnectionState.Fetching == cmdState) { if (null != this.weakDataReaderReference && !this.weakDataReaderReference.IsAlive) { if (_cmdWrapper != null) { _cmdWrapper.FreeKeyInfoStatementHandle(ODBC32.STMT.CLOSE); _cmdWrapper.FreeStatementHandle(ODBC32.STMT.CLOSE); } CloseFromDataReader(); } } } private void DisposeDataReader() { if (null != this.weakDataReaderReference) { IDisposable reader = (IDisposable) this.weakDataReaderReference.Target; if ((null != reader) && this.weakDataReaderReference.IsAlive) { ((IDisposable) reader).Dispose(); } CloseFromDataReader(); } } internal void DisconnectFromDataReaderAndConnection () { // get a reference to the datareader if it is alive OdbcDataReader liveReader = null; if (this.weakDataReaderReference != null){ OdbcDataReader reader; reader = (OdbcDataReader)this.weakDataReaderReference.Target; if (this.weakDataReaderReference.IsAlive) { liveReader = reader; } } // remove reference to this from the live datareader if (liveReader != null) { liveReader.Command = null; } _transaction = null; if (null != _connection) { _connection.RemoveWeakReference(this); _connection = null; } // if the reader is dead we have to dismiss the statement if (liveReader == null){ CloseCommandWrapper(); } // else DataReader now has exclusive ownership _cmdWrapper = null; } override protected void Dispose(bool disposing) { // MDAC 65459 if (disposing) { // release mananged objects // in V1.0, V1.1 the Connection,Parameters,CommandText,Transaction where reset this.DisconnectFromDataReaderAndConnection (); _parameterCollection = null; CommandText = null; } _cmdWrapper = null; // let go of the CommandWrapper _isPrepared = false; base.Dispose(disposing); // notify base classes } internal bool Canceling { get { return _cmdWrapper.Canceling; } } [ ResCategoryAttribute(Res.DataCategory_Data), DefaultValue(""), RefreshProperties(RefreshProperties.All), // MDAC 67707 ResDescriptionAttribute(Res.DbCommand_CommandText), Editor("Microsoft.VSDesigner.Data.Odbc.Design.OdbcCommandTextEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing) ] override public string CommandText { get { string value = _commandText; return ((null != value) ? value : ADP.StrEmpty); } set { if (Bid.TraceOn) { Bid.Trace(" %d#, '", ObjectID); Bid.PutStr(value); // Use PutStr to write out entire string Bid.Trace("'\n"); } if (0 != ADP.SrcCompare(_commandText, value)) { PropertyChanging(); _commandText = value; } } } [ ResCategoryAttribute(Res.DataCategory_Data), ResDescriptionAttribute(Res.DbCommand_CommandTimeout), ] override public int CommandTimeout { // V1.2.3300, XXXCommand V1.0.5000 get { return _commandTimeout; } set { Bid.Trace(" %d#, %d\n", ObjectID, value); if (value < 0) { throw ADP.InvalidCommandTimeout(value); } if (value != _commandTimeout) { PropertyChanging(); _commandTimeout = value; } } } public void ResetCommandTimeout() { // V1.2.3300 if (ADP.DefaultCommandTimeout != _commandTimeout) { PropertyChanging(); _commandTimeout = ADP.DefaultCommandTimeout; } } private bool ShouldSerializeCommandTimeout() { // V1.2.3300 return (ADP.DefaultCommandTimeout != _commandTimeout); } [ DefaultValue(System.Data.CommandType.Text), RefreshProperties(RefreshProperties.All), ResCategoryAttribute(Res.DataCategory_Data), ResDescriptionAttribute(Res.DbCommand_CommandType), ] override public CommandType CommandType { get { CommandType cmdType = _commandType; return ((0 != cmdType) ? cmdType : CommandType.Text); } set { switch(value) { // @perfnote: Enum.IsDefined case CommandType.Text: case CommandType.StoredProcedure: PropertyChanging(); _commandType = value; break; case CommandType.TableDirect: throw ODBC.NotSupportedCommandType(value); default: throw ADP.InvalidCommandType(value); } } } // This will establish a relationship between the command and the connection [ DefaultValue(null), ResCategoryAttribute(Res.DataCategory_Behavior), ResDescriptionAttribute(Res.DbCommand_Connection), Editor("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing), ] new public OdbcConnection Connection { get { return _connection; } set { if (value != _connection) { PropertyChanging(); this.DisconnectFromDataReaderAndConnection(); Debug.Assert(null == _cmdWrapper, "has CMDWrapper when setting connection"); _connection = value; //OnSchemaChanged(); } } } override protected DbConnection DbConnection { // V1.2.3300 get { return Connection; } set { Connection = (OdbcConnection)value; } } override protected DbParameterCollection DbParameterCollection { // V1.2.3300 get { return Parameters; } } override protected DbTransaction DbTransaction { // V1.2.3300 get { return Transaction; } set { Transaction = (OdbcTransaction)value; } } // @devnote: By default, the cmd object is visible on the design surface (i.e. VS7 Server Tray) // to limit the number of components that clutter the design surface, // when the DataAdapter design wizard generates the insert/update/delete commands it will // set the DesignTimeVisible property to false so that cmds won't appear as individual objects [ DefaultValue(true), DesignOnly(true), Browsable(false), EditorBrowsableAttribute(EditorBrowsableState.Never), ] public override bool DesignTimeVisible { // V1.2.3300, XXXCommand V1.0.5000 get { return !_designTimeInvisible; } set { _designTimeInvisible = !value; TypeDescriptor.Refresh(this); // VS7 208845 } } internal bool HasParameters { get { return (null != _parameterCollection); } } [ DesignerSerializationVisibility(DesignerSerializationVisibility.Content), ResCategoryAttribute(Res.DataCategory_Data), ResDescriptionAttribute(Res.DbCommand_Parameters), ] new public OdbcParameterCollection Parameters { get { if (null == _parameterCollection) { _parameterCollection = new OdbcParameterCollection(); } return _parameterCollection; } } [ Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), ResDescriptionAttribute(Res.DbCommand_Transaction), ] new public OdbcTransaction Transaction { get { if ((null != _transaction) && (null == _transaction.Connection)) { _transaction = null; // Dawn of the Dead } return _transaction; } set { if (_transaction != value) { PropertyChanging(); // fire event before value is validated _transaction = value; } } } [ DefaultValue(System.Data.UpdateRowSource.Both), ResCategoryAttribute(Res.DataCategory_Update), ResDescriptionAttribute(Res.DbCommand_UpdatedRowSource), ] override public UpdateRowSource UpdatedRowSource { // V1.2.3300, XXXCommand V1.0.5000 get { return _updatedRowSource; } set { switch(value) { // @perfnote: Enum.IsDefined case UpdateRowSource.None: case UpdateRowSource.OutputParameters: case UpdateRowSource.FirstReturnedRecord: case UpdateRowSource.Both: _updatedRowSource = value; break; default: throw ADP.InvalidUpdateRowSource(value); } } } internal OdbcDescriptorHandle GetDescriptorHandle(ODBC32.SQL_ATTR attribute) { return _cmdWrapper.GetDescriptorHandle(attribute); } // GetStatementHandle // ------------------ // Try to return a cached statement handle. // // Creates a CmdWrapper object if necessary // If no handle is available a handle will be allocated. // Bindings will be unbound if a handle is cached and the bindings are invalid. // internal CMDWrapper GetStatementHandle () { // update the command wrapper object, allocate buffer // create reader object // if (_cmdWrapper==null) { _cmdWrapper = new CMDWrapper(_connection); Debug.Assert(null != _connection, "GetStatementHandle without connection?"); _connection.AddWeakReference(this, OdbcReferenceCollection.CommandTag); } if (_cmdWrapper._dataReaderBuf == null) { _cmdWrapper._dataReaderBuf = new CNativeBuffer(4096); } // if there is already a statement handle we need to do some cleanup // if (null == _cmdWrapper.StatementHandle) { _isPrepared = false; _cmdWrapper.CreateStatementHandle(); } else if ((null != _parameterCollection) && _parameterCollection.RebindCollection) { _cmdWrapper.FreeStatementHandle(ODBC32.STMT.RESET_PARAMS); } return _cmdWrapper; } // OdbcCommand.Cancel() // // In ODBC3.0 ... a call to SQLCancel when no processing is done has no effect at all // (ODBC Programmer's Reference ...) // override public void Cancel() { CMDWrapper wrapper = _cmdWrapper; if (null != wrapper) { wrapper.Canceling = true; OdbcStatementHandle stmt = wrapper.StatementHandle; if (null != stmt) { lock (stmt) { // Cancel the statement ODBC32.RetCode retcode = stmt.Cancel(); // copy of StatementErrorHandler, because stmt may become null switch(retcode) { case ODBC32.RetCode.SUCCESS: case ODBC32.RetCode.SUCCESS_WITH_INFO: // don't fire info message events on cancel break; default: throw wrapper.Connection.HandleErrorNoThrow(stmt, retcode); } } } } } object ICloneable.Clone() { OdbcCommand clone = new OdbcCommand(); Bid.Trace(" %d#, clone=%d#\n", ObjectID, clone.ObjectID); clone.CommandText = CommandText; clone.CommandTimeout = this.CommandTimeout; clone.CommandType = CommandType; clone.Connection = this.Connection; clone.Transaction = this.Transaction; clone.UpdatedRowSource = UpdatedRowSource; if ((null != _parameterCollection) && (0 < Parameters.Count)) { OdbcParameterCollection parameters = clone.Parameters; foreach(ICloneable parameter in Parameters) { parameters.Add(parameter.Clone()); } } return clone; } internal bool RecoverFromConnection() { DisposeDeadDataReader(); return (ConnectionState.Closed == cmdState); } private void CloseCommandWrapper() { CMDWrapper wrapper = _cmdWrapper; if (null != wrapper) { try { wrapper.Dispose(); if (null != _connection) { _connection.RemoveWeakReference(this); } } finally { _cmdWrapper = null; } } } internal void CloseFromConnection () { if (null != _parameterCollection) { _parameterCollection.RebindCollection = true; } DisposeDataReader(); CloseCommandWrapper(); _isPrepared = false; _transaction = null; } internal void CloseFromDataReader() { this.weakDataReaderReference = null; this.cmdState = ConnectionState.Closed; } new public OdbcParameter CreateParameter() { return new OdbcParameter(); } override protected DbParameter CreateDbParameter() { return CreateParameter(); } override protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior) { return ExecuteReader(behavior); } override public int ExecuteNonQuery() { OdbcConnection.ExecutePermission.Demand(); using (OdbcDataReader reader = ExecuteReaderObject(0, ADP.ExecuteNonQuery, false)) { reader.Close(); return reader.RecordsAffected; } } new public OdbcDataReader ExecuteReader() { return ExecuteReader(0/*CommandBehavior*/); } new public OdbcDataReader ExecuteReader(CommandBehavior behavior) { OdbcConnection.ExecutePermission.Demand(); return ExecuteReaderObject(behavior, ADP.ExecuteReader, true); } internal OdbcDataReader ExecuteReaderFromSQLMethod(object[] methodArguments, ODBC32.SQL_API method){ return ExecuteReaderObject(CommandBehavior.Default,method.ToString(),true,methodArguments,method); } private OdbcDataReader ExecuteReaderObject(CommandBehavior behavior, string method, bool needReader) { // MDAC 68324 if ((CommandText == null) || (CommandText.Length == 0)) { throw (ADP.CommandTextRequired(method)); } // using all functions to tell ExecuteReaderObject that return ExecuteReaderObject(behavior,method,needReader,null,ODBC32.SQL_API.SQLEXECDIRECT); } private OdbcDataReader ExecuteReaderObject(CommandBehavior behavior, string method, bool needReader, object[] methodArguments, ODBC32.SQL_API odbcApiMethod) { // MDAC 68324 OdbcDataReader localReader = null; try { DisposeDeadDataReader(); // this is a no-op if cmdState is not Fetching ValidateConnectionAndTransaction(method); // cmdState will change to Executing if(0 != (CommandBehavior.SingleRow & behavior)) { // CommandBehavior.SingleRow implies CommandBehavior.SingleResult behavior |= CommandBehavior.SingleResult; } ODBC32.RetCode retcode; OdbcStatementHandle stmt = GetStatementHandle().StatementHandle; _cmdWrapper.Canceling = false; if(null != weakDataReaderReference) { if(weakDataReaderReference.IsAlive) { object target = weakDataReaderReference.Target; if(null != target && weakDataReaderReference.IsAlive) { if(!((OdbcDataReader)target).IsClosed) { throw ADP.OpenReaderExists(); // MDAC 66411 } } } } localReader = new OdbcDataReader(this, _cmdWrapper, behavior); //Set command properties //Not all drivers support timeout. So fail silently if error if(!Connection.ProviderInfo.NoQueryTimeout) { TrySetStatementAttribute(stmt, ODBC32.SQL_ATTR.QUERY_TIMEOUT, (IntPtr)this.CommandTimeout); } // todo: If we remember the state we can omit a lot of SQLSetStmtAttrW calls ... // if we do not create a reader we do not even need to do that if(needReader) { if(Connection.IsV3Driver) { if(!Connection.ProviderInfo.NoSqlSoptSSNoBrowseTable && !Connection.ProviderInfo.NoSqlSoptSSHiddenColumns) { // Need to get the metadata information //SQLServer actually requires browse info turned on ahead of time... //Note: We ignore any failures, since this is SQLServer specific //We won't specialcase for SQL Server but at least for non-V3 drivers if(localReader.IsBehavior(CommandBehavior.KeyInfo)) { if(!_cmdWrapper._ssKeyInfoModeOn) { TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE, (IntPtr)ODBC32.SQL_NB.ON); TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS, (IntPtr)ODBC32.SQL_HC.ON); _cmdWrapper._ssKeyInfoModeOff = false; _cmdWrapper._ssKeyInfoModeOn = true; } } else { if(!_cmdWrapper._ssKeyInfoModeOff) { TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE, (IntPtr)ODBC32.SQL_NB.OFF); TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS, (IntPtr)ODBC32.SQL_HC.OFF); _cmdWrapper._ssKeyInfoModeOff = true; _cmdWrapper._ssKeyInfoModeOn = false; } } } } } if(localReader.IsBehavior(CommandBehavior.KeyInfo) || localReader.IsBehavior(CommandBehavior.SchemaOnly)) { retcode = stmt.Prepare(CommandText); if(ODBC32.RetCode.SUCCESS != retcode) { _connection.HandleError(stmt, retcode); } } bool mustRelease = false; CNativeBuffer parameterBuffer = _cmdWrapper._nativeParameterBuffer; RuntimeHelpers.PrepareConstrainedRegions(); try { //Handle Parameters //Note: We use the internal variable as to not instante a new object collection, //for the the common case of using no parameters. if((null != _parameterCollection) && (0 < _parameterCollection.Count)) { int parameterBufferSize = _parameterCollection.CalcParameterBufferSize(this); if(null == parameterBuffer || parameterBuffer.Length < parameterBufferSize) { if (null != parameterBuffer) { parameterBuffer.Dispose(); } parameterBuffer = new CNativeBuffer(parameterBufferSize); _cmdWrapper._nativeParameterBuffer = parameterBuffer; } else { parameterBuffer.ZeroMemory(); } parameterBuffer.DangerousAddRef(ref mustRelease); _parameterCollection.Bind(this, _cmdWrapper, parameterBuffer); } if(!localReader.IsBehavior(CommandBehavior.SchemaOnly)) { // Can't get the KeyInfo after command execution (SQL Server only since it does not support multiple // results on the same connection). Stored procedures (SP) do not return metadata before actual execution // Need to check the column count since the command type may not be set to SP for a SP. if((localReader.IsBehavior(CommandBehavior.KeyInfo) || localReader.IsBehavior(CommandBehavior.SchemaOnly)) && (CommandType != CommandType.StoredProcedure)) { Int16 cColsAffected; retcode = stmt.NumberOfResultColumns(out cColsAffected); if(retcode == ODBC32.RetCode.SUCCESS || retcode == ODBC32.RetCode.SUCCESS_WITH_INFO) { if(cColsAffected > 0) { localReader.GetSchemaTable(); } } else if(retcode == ODBC32.RetCode.NO_DATA) { // do nothing } else { // any other returncode indicates an error _connection.HandleError(stmt, retcode); } } switch(odbcApiMethod) { case ODBC32.SQL_API.SQLEXECDIRECT: if(localReader.IsBehavior(CommandBehavior.KeyInfo) || _isPrepared) { //Already prepared, so use SQLExecute retcode = stmt.Execute(); // Build metadata here // localReader.GetSchemaTable(); } else { #if DEBUG //if (AdapterSwitches.OleDbTrace.TraceInfo) { // ADP.DebugWriteLine("SQLExecDirectW: " + CommandText); //} #endif //SQLExecDirect retcode = stmt.ExecuteDirect(CommandText); } break; case ODBC32.SQL_API.SQLTABLES: retcode = stmt.Tables((string)methodArguments[0], //TableCatalog (string)methodArguments[1], //TableSchema, (string)methodArguments[2], //TableName (string)methodArguments[3]); //TableType break; case ODBC32.SQL_API.SQLCOLUMNS: retcode = stmt.Columns((string)methodArguments[0], //TableCatalog (string)methodArguments[1], //TableSchema (string)methodArguments[2], //TableName (string)methodArguments[3]); //ColumnName break; case ODBC32.SQL_API.SQLPROCEDURES: retcode = stmt.Procedures((string)methodArguments[0], //ProcedureCatalog (string)methodArguments[1], //ProcedureSchema (string)methodArguments[2]); //procedureName break; case ODBC32.SQL_API.SQLPROCEDURECOLUMNS: retcode = stmt.ProcedureColumns((string)methodArguments[0], //ProcedureCatalog (string)methodArguments[1], //ProcedureSchema (string)methodArguments[2], //procedureName (string)methodArguments[3]); //columnName break; case ODBC32.SQL_API.SQLSTATISTICS: retcode = stmt.Statistics((string)methodArguments[0], //TableCatalog (string)methodArguments[1], //TableSchema (string)methodArguments[2], //TableName (Int16)methodArguments[3], //IndexTrpe (Int16)methodArguments[4]); //Accuracy break; case ODBC32.SQL_API.SQLGETTYPEINFO: retcode = stmt.GetTypeInfo((Int16)methodArguments[0]); //SQL Type break; default: // this should NEVER happen Debug.Assert(false, "ExecuteReaderObjectcalled with unsupported ODBC API method."); throw ADP.InvalidOperation(method.ToString()); } //Note: Execute will return NO_DATA for Update/Delete non-row returning queries if((ODBC32.RetCode.SUCCESS != retcode) && (ODBC32.RetCode.NO_DATA != retcode)) { _connection.HandleError(stmt, retcode); } } // end SchemaOnly } finally { if(mustRelease) { parameterBuffer.DangerousRelease(); } } this.weakDataReaderReference = new WeakReference(localReader); // XXXCommand.Execute should position reader on first row returning result // any exceptions in the initial non-row returning results should be thrown // from from ExecuteXXX not the DataReader if(!localReader.IsBehavior(CommandBehavior.SchemaOnly)) { localReader.FirstResult(); } cmdState = ConnectionState.Fetching; } finally { if(ConnectionState.Fetching != cmdState) { if(null != localReader) { // clear bindings so we don't grab output parameters on a failed execute if(null != _parameterCollection) { _parameterCollection.ClearBindings(); } ((IDisposable)localReader).Dispose(); } if(ConnectionState.Closed != cmdState) { cmdState = ConnectionState.Closed; } } } return localReader; } override public object ExecuteScalar() { OdbcConnection.ExecutePermission.Demand(); object value = null; using(IDataReader reader = ExecuteReaderObject(0, ADP.ExecuteScalar, false)) { if (reader.Read() && (0 < reader.FieldCount)) { value = reader.GetValue(0); } reader.Close(); } return value; } internal string GetDiagSqlState() { return _cmdWrapper.GetDiagSqlState(); } private void PropertyChanging() { _isPrepared = false; } // Prepare // // if the CommandType property is set to TableDirect Prepare does nothing. // if the CommandType property is set to StoredProcedure Prepare should succeed but result // in a no-op // // throw InvalidOperationException // if the connection is not set // if the connection is not open // override public void Prepare() { OdbcConnection.ExecutePermission.Demand(); ODBC32.RetCode retcode; ValidateOpenConnection(ADP.Prepare); if (0 != (ConnectionState.Fetching & _connection.InternalState)) { throw ADP.OpenReaderExists(); } if (CommandType == CommandType.TableDirect) { return; // do nothing } DisposeDeadDataReader(); GetStatementHandle(); OdbcStatementHandle stmt = _cmdWrapper.StatementHandle; retcode = stmt.Prepare(CommandText); if (ODBC32.RetCode.SUCCESS != retcode) { _connection.HandleError(stmt, retcode); } _isPrepared = true; } void TrySetStatementAttribute (OdbcStatementHandle stmt, ODBC32.SQL_ATTR stmtAttribute, IntPtr value) { ODBC32.RetCode retcode = stmt.SetStatementAttribute( stmtAttribute, value, ODBC32.SQL_IS.UINTEGER); if (retcode == ODBC32.RetCode.ERROR) { string sqlState; stmt.GetDiagnosticField(out sqlState); if ((sqlState == "HYC00") || (sqlState == "HY092")) { Connection.FlagUnsupportedStmtAttr(stmtAttribute); } else { // now what? Should we throw? } } } private void ValidateOpenConnection(string methodName) { // see if we have a connection OdbcConnection connection = Connection; if (null == connection) { throw ADP.ConnectionRequired(methodName); } // must have an open and available connection ConnectionState state = connection.State; if (ConnectionState.Open != state) { throw ADP.OpenConnectionRequired(methodName, state); } } private void ValidateConnectionAndTransaction(string method) { if (null == _connection) { throw ADP.ConnectionRequired(method); } _transaction = _connection.SetStateExecuting(method, Transaction); cmdState = ConnectionState.Executing; } } sealed internal class CMDWrapper { private OdbcStatementHandle _stmt; // hStmt private OdbcStatementHandle _keyinfostmt; // hStmt for keyinfo internal OdbcDescriptorHandle _hdesc; // hDesc internal CNativeBuffer _nativeParameterBuffer; // Native memory for internal memory management // (Performance optimization) internal CNativeBuffer _dataReaderBuf; // Reusable DataReader buffer private readonly OdbcConnection _connection; // Connection private bool _canceling; // true if the command is canceling internal bool _hasBoundColumns; internal bool _ssKeyInfoModeOn; // tells us if the SqlServer specific options are on internal bool _ssKeyInfoModeOff; // a tri-state value would be much better ... internal CMDWrapper (OdbcConnection connection) { _connection = connection; } internal bool Canceling { get { return _canceling; } set { _canceling = value; } } internal OdbcConnection Connection { get { return _connection; } } internal bool HasBoundColumns { // get { // return _hasBoundColumns; // } set { _hasBoundColumns = value; } } internal OdbcStatementHandle StatementHandle { get { return _stmt; } } internal OdbcStatementHandle KeyInfoStatement { get { return _keyinfostmt; } } internal void CreateKeyInfoStatementHandle() { DisposeKeyInfoStatementHandle(); _keyinfostmt = _connection.CreateStatementHandle(); } internal void CreateStatementHandle() { DisposeStatementHandle(); _stmt = _connection.CreateStatementHandle(); } internal void Dispose() { if (null != _dataReaderBuf) { _dataReaderBuf.Dispose(); _dataReaderBuf = null; } DisposeStatementHandle(); CNativeBuffer buffer = _nativeParameterBuffer; _nativeParameterBuffer = null; if (null != buffer) { buffer.Dispose(); } _ssKeyInfoModeOn = false; _ssKeyInfoModeOff = false; } private void DisposeDescriptorHandle() { OdbcDescriptorHandle handle = _hdesc; if (null != handle) { _hdesc = null; handle.Dispose(); } } internal void DisposeStatementHandle() { DisposeKeyInfoStatementHandle(); DisposeDescriptorHandle(); OdbcStatementHandle handle = _stmt; if (null != handle) { _stmt = null; handle.Dispose(); } } internal void DisposeKeyInfoStatementHandle() { OdbcStatementHandle handle = _keyinfostmt; if (null != handle) { _keyinfostmt = null; handle.Dispose(); } } internal void FreeStatementHandle(ODBC32.STMT stmt) { DisposeDescriptorHandle(); OdbcStatementHandle handle = _stmt; if (null != handle) { try { ODBC32.RetCode retcode; retcode = handle.FreeStatement(stmt); StatementErrorHandler(retcode); } catch (Exception e) { // if (ADP.IsCatchableExceptionType(e)) { _stmt = null; handle.Dispose(); } throw; } } } internal void FreeKeyInfoStatementHandle(ODBC32.STMT stmt) { OdbcStatementHandle handle = _keyinfostmt; if (null != handle) { try { handle.FreeStatement(stmt); } catch (Exception e) { // if (ADP.IsCatchableExceptionType(e)) { _keyinfostmt = null; handle.Dispose(); } throw; } } } // Get the Descriptor Handle for the current statement // internal OdbcDescriptorHandle GetDescriptorHandle(ODBC32.SQL_ATTR attribute) { OdbcDescriptorHandle hdesc = _hdesc; if (null == _hdesc) { _hdesc = hdesc = new OdbcDescriptorHandle(_stmt, attribute); } return hdesc; } internal string GetDiagSqlState () { string sqlstate; _stmt.GetDiagnosticField(out sqlstate); return sqlstate; } internal void StatementErrorHandler(ODBC32.RetCode retcode) { switch(retcode) { case ODBC32.RetCode.SUCCESS: case ODBC32.RetCode.SUCCESS_WITH_INFO: _connection.HandleErrorNoThrow(_stmt, retcode); break; default: throw _connection.HandleErrorNoThrow(_stmt, retcode); } } internal void UnbindStmtColumns() { if (_hasBoundColumns) { FreeStatementHandle(ODBC32.STMT.UNBIND); _hasBoundColumns = false; } } } }