Using TransactionScopeOption.Suppress with Sql Server Compact 4

6.3k views Asked by At

I'm having trouble suppressing part of a transaction using Sql Server CE 4 with Entity Framework and System.Transactions.TransactionScope.

The simplified code below is from a unit test demonstrating the problem.

The idea is to enable the innerScope block (with no transaction) to succeed or fail without affecting the outerScope block (the "ambient" transaction). This is the stated purpose of TransactionScopeOption.Suppress.

However, the code fails because it seems that the entire SomeTable table is locked by the first insert in outerScope. At the point indicated in the code, this error is thrown:

"SQL Server Compact timed out waiting for a lock. The default lock time is 2000ms for devices and 5000ms for desktops. The default lock timeout can be increased in the connection string using the ssce: default lock timeout property. [ Session id = 2,Thread id = 2248,Process id = 13516,Table name = SomeTable,Conflict type = x lock (x blocks),Resource = PAG (idx): 1046 ]"

[TestMethod()]
[DeploymentItem("MyLocalDb.sdf")]
public void MyLocalDb_TransactionSuppressed()
{
    int count = 0;

    // This is the ambient transaction
    using (TransactionScope outerScope = new TransactionScope(TransactionScopeOption.Required))
    {
        using (MyObjectContext outerContext = new MyObjectContext())
        {
            // Do something in the outer scope
            outerContext.Connection.Open();
            outerContext.AddToSomeTable(CreateSomeTableRow());
            outerContext.SaveChanges();
            try
            {
                // Ambient transaction is suppressed for the inner scope of SQLCE operations
                using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.Suppress))
                {
                    using (MyObjectContext innerContext = new MyObjectContext())
                    {
                        innerContext.Connection.Open();
                        // This insert will work
                        innerContext.AddToSomeTable(CreateSomeTableRow());
                        innerContext.SaveChanges(); // ====> EXCEPTION THROWN HERE
                        // There will be other, possibly failing operations here
                    }
                    innerScope.Complete();
                }
            }
            catch { }
        }
        outerScope.Complete();
    }

    count = GetCountFromSomeTable();
    // The insert in the outer scope should succeed, and the one from the inner scope
    Assert.AreEqual(2, count);
}

So, it seems that "a transaction in a transaction scope executes with isolation level set to Serializable", according to http://msdn.microsoft.com/en-us/library/ms172001

However, using the following code snippet to change the isolation level of the TransactionScope does not help:

public void MyLocalDb_TransactionSuppressed()
{
    TransactionOptions opts = new TransactionOptions();
    opts.IsolationLevel = IsolationLevel.ReadCommitted;
    int count = 0;

    // This is the ambient transaction
    using (TransactionScope outerScope = new TransactionScope(TransactionScopeOption.Required, opts))
    ...

The same exception is thrown at the same location.

It seems the only way to avoid this is to call outerScope.Complete() before entering the innerScope block. But this would defeat the purpose.

What am I missing here? Thanks.

2

There are 2 answers

2
josemiguel.torres On

AFAIK SQL Server Compact does not support nested transactions.

5
Ladislav Mrnka On

And why do you do that this way? If I look at your code there is no difference between running the second transaction scope inside the first one and running them in sequence.

IMHO this is not a problem of SQL Compact, TransactionScope or isolation level. This is a problem of your wrong application logic.

Each SaveChanges runs in transaction - either outer transaction defined by TransactionScope or inner DbTransaction. Even if it would not create transaction every database command has its own implicit transaction. If you use Suppress on the inner code block you are creating two concurrent transactions which are trying to insert into same table and moreover the first cannot complete without completing the second and the second cannot complete without completing the first => deadlock.

The reason is that insert command always locks part of the table not allowing new inserts until it is committed or rolled back. I'm not sure if this can be avoided by changing transaction isolation level - if it does, you will most probably need Read.Uncommitted.