// // System.Data.Odbc.OdbcTransaction // // Authors: // Brian Ritchie (brianlritchie@hotmail.com) // // Copyright (C) Brian Ritchie, 2002 // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Data; using System.Data.Common; using System.Globalization; namespace System.Data.Odbc { #if NET_2_0 public sealed class OdbcTransaction : DbTransaction, IDisposable #else public sealed class OdbcTransaction : MarshalByRefObject, IDbTransaction #endif { private bool disposed; private OdbcConnection connection; private IsolationLevel isolationlevel; private bool isOpen; internal OdbcTransaction (OdbcConnection conn, IsolationLevel isolationlevel) { // Set Auto-commit (102) to false SetAutoCommit (conn, false); // Handle isolation level OdbcIsolationLevel lev = OdbcIsolationLevel.ReadCommitted; OdbcConnectionAttribute attr = OdbcConnectionAttribute.TransactionIsolation; switch (isolationlevel) { case IsolationLevel.ReadUncommitted: lev = OdbcIsolationLevel.ReadUncommitted; break; case IsolationLevel.ReadCommitted: lev = OdbcIsolationLevel.ReadCommitted; break; case IsolationLevel.RepeatableRead: lev = OdbcIsolationLevel.RepeatableRead; break; case IsolationLevel.Serializable: lev = OdbcIsolationLevel.Serializable; break; #if NET_2_0 case IsolationLevel.Snapshot: // badly broken on MS: // https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=305736 lev = OdbcIsolationLevel.Snapshot; // SQL_ATTR_TXN_ISOLATION can be used to set all other isolation // levels except for SQL_TXN_SS_SNAPSHOT. If you want to use snapshot // isolation, you must set SQL_TXN_SS_SNAPSHOT through // SQL_COPT_SS_TXN_ISOLATION. However, you can retrieve the // isolation level by using either SQL_ATTR_TXN_ISOLATION or // SQL_COPT_SS_TXN_ISOLATION. // Source: // http://msdn2.microsoft.com/en-us/library/ms131709.aspx attr = OdbcConnectionAttribute.CoptTransactionIsolation; break; #endif case IsolationLevel.Unspecified: // when isolationlevel is not specified, then use // default isolation level of the driver and // lazy initialize it in the IsolationLevel property break; #if NET_2_0 case IsolationLevel.Chaos: throw new ArgumentOutOfRangeException ("IsolationLevel", string.Format (CultureInfo.CurrentCulture, "The IsolationLevel enumeration " + "value, {0}, is not supported by " + "the .Net Framework Odbc Data " + "Provider.", (int) isolationlevel)); #endif default: #if NET_2_0 throw new ArgumentOutOfRangeException ("IsolationLevel", string.Format (CultureInfo.CurrentCulture, "The IsolationLevel enumeration value, {0}, is invalid.", (int) isolationlevel)); #else throw new ArgumentException (string.Format ( CultureInfo.InvariantCulture, "Not supported isolationlevel - {0}", isolationlevel)); #endif } // only change isolation level if it was explictly set if (isolationlevel != IsolationLevel.Unspecified) { // mbd: Getting the return code of the second call to SQLSetConnectAttr is missing from original code! OdbcReturn ret = libodbc.SQLSetConnectAttr (conn.hDbc, attr, (IntPtr) lev, 0); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw conn.CreateOdbcException (OdbcHandleType.Dbc, conn.hDbc); } this.isolationlevel = isolationlevel; connection = conn; isOpen = true; } // Set Auto-commit (102) connection attribute // [MonoTODO]: nice to have before svn: define libodbc.SQL_IS_UINTEGER = -5 private static void SetAutoCommit (OdbcConnection conn, bool isAuto) { OdbcReturn ret = libodbc.SQLSetConnectAttr (conn.hDbc, OdbcConnectionAttribute.AutoCommit, (IntPtr) (isAuto ? 1 : 0), -5); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw conn.CreateOdbcException (OdbcHandleType.Dbc, conn.hDbc); } private static IsolationLevel GetIsolationLevel (OdbcConnection conn) { int lev; int length; OdbcReturn ret = libodbc.SQLGetConnectAttr (conn.hDbc, OdbcConnectionAttribute.TransactionIsolation, out lev, 0, out length); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw conn.CreateOdbcException (OdbcHandleType.Dbc, conn.hDbc); return MapOdbcIsolationLevel ((OdbcIsolationLevel) lev); } private static IsolationLevel MapOdbcIsolationLevel (OdbcIsolationLevel odbcLevel) { IsolationLevel isoLevel = IsolationLevel.Unspecified; switch (odbcLevel) { case OdbcIsolationLevel.ReadUncommitted: isoLevel = IsolationLevel.ReadUncommitted; break; case OdbcIsolationLevel.ReadCommitted: isoLevel = IsolationLevel.ReadCommitted; break; case OdbcIsolationLevel.RepeatableRead: isoLevel = IsolationLevel.RepeatableRead; break; case OdbcIsolationLevel.Serializable: isoLevel = IsolationLevel.Serializable; break; #if NET_2_0 case OdbcIsolationLevel.Snapshot: isoLevel = IsolationLevel.Snapshot; break; #else default: throw new NotSupportedException (string.Format ( CultureInfo.InvariantCulture, "Isolation level {0} is not supported.", odbcLevel)); #endif } return isoLevel; } #region Implementation of IDisposable #if NET_2_0 protected override #endif void Dispose (bool disposing) { if (!disposed) { if (disposing && isOpen) Rollback (); disposed = true; } } void IDisposable.Dispose () { Dispose (true); GC.SuppressFinalize (this); } #endregion Implementation of IDisposable #region Implementation of IDbTransaction public #if NET_2_0 override #endif //NET_2_0 void Commit () { if (!isOpen) throw ExceptionHelper.TransactionNotUsable (GetType ()); if (connection.transaction == this) { OdbcReturn ret = libodbc.SQLEndTran ((short) OdbcHandleType.Dbc, connection.hDbc, 0); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw connection.CreateOdbcException (OdbcHandleType.Dbc, connection.hDbc); SetAutoCommit (connection, true); // restore default auto-commit connection.transaction = null; connection = null; isOpen = false; } else throw new InvalidOperationException (); } public #if NET_2_0 override #endif //NET_2_0 void Rollback() { if (!isOpen) throw ExceptionHelper.TransactionNotUsable (GetType ()); if (connection.transaction == this) { OdbcReturn ret = libodbc.SQLEndTran ((short) OdbcHandleType.Dbc, connection.hDbc, 1); if (ret != OdbcReturn.Success && ret != OdbcReturn.SuccessWithInfo) throw connection.CreateOdbcException (OdbcHandleType.Dbc, connection.hDbc); SetAutoCommit (connection, true); // restore default auto-commit connection.transaction = null; connection = null; isOpen = false; } else throw new InvalidOperationException (); } #if NET_2_0 protected override DbConnection DbConnection { get { return Connection; } } #else IDbConnection IDbTransaction.Connection { get { return Connection; } } #endif public #if NET_2_0 override #endif IsolationLevel IsolationLevel { get { if (!isOpen) throw ExceptionHelper.TransactionNotUsable (GetType ()); if (isolationlevel == IsolationLevel.Unspecified) isolationlevel = GetIsolationLevel (Connection); return isolationlevel; } } #endregion Implementation of IDbTransaction #region Public Instance Properties public new OdbcConnection Connection { get { return connection; } } #endregion Public Instance Properties } }