Question

When I do an insert with the SQL Anywhere 16 .net provider, it creates a shared lock on the table. even with a commit afterwards. How do I prevent it? (Or what am I doing wrong?)

    DbCommand command = new SACommand();
    command.CommandTimeout = this.Timeout;
    bool mustCloseConnection = false;
    PrepareCommand(command, connection, null, commandType, commandText, commandParameters, ref mustCloseConnection);
    int num2 = command.ExecuteNonQuery();
    command.Parameters.Clear();
    if (mustCloseConnection)
    {
        connection.Close();
    }


    private void PrepareCommand(IDbCommand command, IDbConnection connection, IDbTransaction transaction, CommandType commandType, string commandText, IDataParameter[] commandParameters, ref bool mustCloseConnection)
    {
        if (command == null)
        {
            throw new ArgumentNullException("command");
        }
        if ((commandText == null) || (commandText.Length == 0))
        {
            throw new ArgumentNullException("commandText");
        }
        if (connection.State != ConnectionState.Open)
        {
            connection.Open();
            mustCloseConnection = true;
        }
        else
        {
            mustCloseConnection = false;
        }
        command.Connection = connection;
        command.CommandText = commandText;
        command.CommandTimeout = this.Timeout;
        if (transaction != null)
        {
            if (transaction.Connection == null)
            {
                throw new ArgumentException("The transaction was rollbacked or commited, please provide an open transaction.", "transaction");
            }
            command.Transaction = transaction;
        }
        command.CommandType = commandType;
        if (commandParameters != null)
        {
            AttachParameters(command, commandParameters);
        }
    }
Was it helpful?

Solution

Ok, lets check some documentation:

SQL Anywhere® Server - Programming > Using SQL in Applications > Controlling transactions in applications > Cursors and transactions
In general, a cursor closes when a COMMIT is performed. There are two exceptions to this behavior:

The close_on_endtrans database option is set to Off.

**A cursor is opened WITH HOLD**, which is the default with Open Client and JDBC.

If either of these two cases is true, the cursor remains open on a COMMIT.

And that One: http://infocenter.sybase.com/help/topic/com.sybase.help.sqlanywhere.12.0.1/pdf/dbprogramming12.pdf

Cursor behavior when opening cursors
You can configure the following aspects of cursor behavior when you open the cursor:
● Isolation level You can explicitly set the isolation level of operations on a cursor to be different
from the current isolation level of the transaction. To do this, set the isolation_level option.
● Holding By default, cursors in embedded SQL close at the end of a transaction. Opening a cursor
WITH HOLD allows you to keep it open until the end of a connection, or until you explicitly close it.
ADO.NET, ODBC, JDBC, and Open Client leave cursors open at the end of transactions by default

Maybe there are some more fresh docs, but I don't thing anything has been changed.. I'm almost sure you have WITH HOLD Cursor so even after COMMIT it remains open.

Actually you already have that answer, just without explanation why you should close the connection..

I hope this helps...

OTHER TIPS

You should Dispose your Connection and Command Object. Call Dispose or wrap it in a using statement like this: (code just typed for example)

using (IDBConnection con = new DbConnection())
{
  using (IDBCommand com = new DbCommand())
  {
    // do your sql stuff here
  }
  con.Close();
}

I've had such errors with MSSQL because I haven't disposed my command object.

If you are 100% sure all your connections, commands, transactions, etc. are getting gracefully disposed, then you might want to check how you are instantiating your transactions.

The TransactionScope default constructor was incorrectly designed by Microsoft and sooner or later it causes locks (similar issues could apply to other transaction classes). The default constructor utilize the "Serializable isolation level" which is prone to cause locks.

The issue is even worse: Should you instantiate an TransactionScope with a better isolation level (for example, ReadCommitted), in the event that your SQL operation fails and performs a rollback, it will internally change the isolation level again to "Serializable", which will be prone to locks to any other command using the same transaction instance.

This was reported as a bug to Microsoft, and MS simply replied with the typical "Well, if this is a bug, we can't fix it because it could affect systems out there, and why do you even create TransactionScope instances using the default constructor? You shouldn't do that ever" excuse.

The only safe-way to use then the TransactionScope class, is to ALWAYS AND EACH TIME instantiate it using explicitly the desired isolation level that doesn't cause locks (the usual scenario is using ReadCommitted, but you could use others).

You can either use a static builder as proposed in here http://blogs.msdn.com/b/dbrowne/archive/2010/06/03/using-new-transactionscope-considered-harmful.aspx?Redirected=true :

public class TransactionUtils {
  public static TransactionScope CreateTransactionScope()
  {
    var transactionOptions = new TransactionOptions();
    transactionOptions.IsolationLevel = IsolationLevel.ReadCommitted;
    transactionOptions.Timeout = TransactionManager.MaximumTimeout;
    return new TransactionScope(TransactionScopeOption.Required, transactionOptions);
  }
}

Or using a custom wrapper class:

namespace System.Transactions {
    /**************************************************************************************************************
     * IMPORTANT: This class must ALWAYS be used instead of the regular "TransactionScope" class. This class
     * enforces having transactions that read "only committed" data.
     * 
     * This is because the implementation of TransactionScope is faulty (wrong design) and it gives issues because
     * it changes the connections available at the the connection pool. To read more, check this link:
     * http://blogs.msdn.com/b/dbrowne/archive/2010/05/21/using-new-transactionscope-considered-harmful.aspx
     * 
     * The namespace was set to "System.Transactions" in order to provide ease of use when updating legacy code
     * that was using the old class
     **************************************************************************************************************/
    public class SafeTransactionScope : IDisposable {
        private TransactionScope _transactionScope;
        private bool _disposed;

        #region Constructors
        public SafeTransactionScope()
            : this(TransactionManager.MaximumTimeout) {
        }

        public SafeTransactionScope(TimeSpan scopeTimeout)
            : this(TransactionScopeOption.Required, scopeTimeout) {
        }

        public SafeTransactionScope(TransactionScopeOption scopeOption)
            : this(scopeOption, TransactionManager.MaximumTimeout) {
        }

        public SafeTransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout) {
            this._disposed = false;
            this._transactionScope = CreateTransactionScope(scopeOption, scopeTimeout);
        } 
        #endregion

        #region Disposing methods
        public void Dispose() {
            Dispose(true);

            // Use SupressFinalize in case a subclass 
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing) {
            if(!this._disposed) {
                if(disposing) {
                    if(this._transactionScope != null) {
                        this._transactionScope.Dispose();
                        this._transactionScope = null;
                    }
                }

                // Indicate that the instance has been disposed.
                this._disposed = true;
            }
        } 
        #endregion

        public void Complete() {
            if(this._disposed) {
                throw new ObjectDisposedException("SafeTransactionScope");
            }

            if(this._transactionScope == null) {
                // This should never happen
                throw new ObjectDisposedException("SafeTransactionScope._transactionScope");
            }

            this._transactionScope.Complete();
        }

        private static TransactionScope CreateTransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout) {
            var transactionOptions = new TransactionOptions() {
                IsolationLevel = IsolationLevel.ReadCommitted,
                Timeout = scopeTimeout
            };

            return new TransactionScope(scopeOption, transactionOptions);
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top