Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby wscalf » Wed 12 Dec 2012 00:42

While running our integration tests against Oracle via the devart dotConnect for Oracle provider, we encountered some intermittent, strange behavior in some tests that use multiple instances of the application's DbContext class within an MSDTC transaction set to isolation level ReadCommitted.

Sometimes the tests pass, and sometimes they don't. When they fail, it appears to be because the DbContext that wasn't used to do the work doesn't appear to be aware of the changes made by the other DbContext. When running the same tests against Sql Server or without the MSDTC transaction, they appear to pass consistently. This behavior also applies if the tests are run individually.

For now, my plan is to adjust the tests so that they only use one DbContext instance per thread, which is also how the application behaves (enforced by a DI container), but why would this happen in the first place? ..And would one-context-per-thread really fix it?

I've also included some repro code below - with the necessary project references and an appropriate connection string, it should compile, run, and demonstrate what I'm describing (the 'Single_context_in_transaction' test passes consistently, while the 'Multiple_contexts_in_transaction' passes sometimes and fails sometimes.)

Code: Select all
using System;
using System.Linq;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Data.SqlClient;
using System.Transactions;
using Devart.Data.Oracle;
using Devart.Data.Oracle.Entity.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace InconsistentTransactionBehaviorRepro
{
    [TestClass]
    public class UnitTest1
    {
        private readonly Context ClassContext;

        public UnitTest1()
        {
            ClassContext = new Context();
        }

        [TestMethod]
        public void Multiple_contexts_within_transaction()
        {
            var localContext = new Context();

            RunTestInTransaction(ClassContext, localContext);
        }

        [TestMethod]
        public void Single_context_within_transaction()
        {
            RunTestInTransaction(ClassContext, ClassContext);
        }

        private void RunTestInTransaction(Context outerContext, Context innerContext)
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required,
                                new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
            {
                var initialCount = innerContext.Items.Count();

                outerContext.Items.Add(new Item() { Name = "Foo" });
                outerContext.SaveChanges();

                var finalCount = innerContext.Items.Count();

                Assert.AreEqual(initialCount + 1, finalCount);
            }
        }
    }

    #region Entity Framework Stuff
    class Item
    {
        public int ItemID { get; set; }
        public string Name { get; set; }
    }

    class ItemMap : EntityTypeConfiguration<Item>
    {
        public ItemMap()
        {
            this.HasKey(i => i.ItemID);

            this.Property(i => i.Name)
                .IsRequired()
                .IsVariableLength()
                .HasMaxLength(25)
                .IsUnicode(false);

            this.ToTable("Item");
        }
    }

    class Context : DbContext
    {
        static Context()
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
        }

        public Context()
            : base("Name=Context")
        {
        }

        public IDbSet<Item> Items { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            if (Database.Connection is SqlConnection)
            {
                //Sql Server specific configuration:
            }
            else if (Database.Connection is OracleConnection)
            {
                //Oracle specific configuration:

                // You use the capability for configuring the behavior of the EF-provider:
                var config = OracleEntityProviderConfig.Instance;

                // Now, you switch off schema name generation while generating DDL scripts and DML:
                config.Workarounds.IgnoreSchemaName = true;
                config.Workarounds.ColumnTypeCasingConventionCompatibility = true;
                config.SqlFormatting.Disable();
                config.DatabaseScript.Schema.DeleteDatabaseBehaviour = DeleteDatabaseBehaviour.AllSchemaObjects;
                //--------------------------------------------------------------

                modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.ColumnTypeCasingConvention>();

            }
            else
            {
                throw new InvalidOperationException("Attempted to initialize context against an unrecognized database back-end.");
            }

            modelBuilder.Configurations.Add(new ItemMap());
        }
    }

    #endregion
}
wscalf
 
Posts: 7
Joined: Wed 12 Dec 2012 00:18

Re: Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby Shalex » Thu 20 Dec 2012 15:19

wscalf wrote:Sometimes the tests pass, and sometimes they don't. When they fail, it appears to be because the DbContext that wasn't used to do the work doesn't appear to be aware of the changes made by the other DbContext.

We cannot reproduce the problem with 'Multiple_contexts_in_transaction' using dotConnect for Oracle v 7.4.146 and EF v 5.0.
1. Are you running serveral tests with 'Multiple_contexts_in_transaction' simultaneously (in parallel threads)? Try executing them consequentially.
2. Have you specified the "Persist Security Info=True" parameter in your connection string? (http://forums.devart.com/viewtopic.php?f=1&t=25299#p86322)
Shalex
Devart Team
 
Posts: 7781
Joined: Thu 14 Aug 2008 12:44

Re: Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby wscalf » Thu 20 Dec 2012 19:43

Hm. This is my current connection string:
Code: Select all
<add name="Context" connectionString="User Id=devartTest;Password= devartTest;Server=10.10.2.52;Direct=True;Sid=SIMG;Port=1521;Persist Security Info=True;Pooling=False" providerName="Devart.Data.Oracle" />


..which does have Persist Security Info=True in it.

The tests in the sample above are the only ones in the solution, and the behavior I'm describing seems to occur whether one or both tests are executed at once.

Because it's intermittent, it can take a few tries before the one test fails, but in any sequence of five runs I've done, it has always failed eventually. If you can't reproduce it, that makes me wonder if it's something misbehaving in our environment..
wscalf
 
Posts: 7
Joined: Wed 12 Dec 2012 00:18

Re: Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby Shalex » Wed 26 Dec 2012 14:47

wscalf wrote:If you can't reproduce it, that makes me wonder if it's something misbehaving in our environment..

We still cannot reproduce the problem.
If possible, please try running the test on another workstation (or virtual machine). Does the issue persist in this case as well?
Shalex
Devart Team
 
Posts: 7781
Joined: Thu 14 Aug 2008 12:44

Re: Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby wscalf » Wed 26 Dec 2012 18:07

We've ran the tests from two different workstations (both developer machines) against two different Oracle instances (one in a VM on a peer's laptop and one on a Windows server in the closet), and we have the same behavior with all four permutations.
wscalf
 
Posts: 7
Joined: Wed 12 Dec 2012 00:18

Re: Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby Shalex » Fri 28 Dec 2012 08:16

Please specify the following information:
1) the build number (x.xx.xxx) of your dotConnect for Oracle;
2) the version of .NET Framework used by your project;
3) the version of Entity Framework;
4) the Oracle version.
Shalex
Devart Team
 
Posts: 7781
Joined: Thu 14 Aug 2008 12:44

Re: Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby wscalf » Wed 09 Jan 2013 22:32

Sorry for the delay - the requested information is below:
dotConnect for Oracle build: 7.4.146
.NET Framework version: 4.5
Entity Framework version: 5.0
Oracle version: 11g
wscalf
 
Posts: 7
Joined: Wed 12 Dec 2012 00:18

Re: Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby Shalex » Mon 14 Jan 2013 15:13

We have reproduced the problem.

The Direct mode connections are enlisted in distributed transaction. But there will be only emulation of TransactionScope support in this case, because two-phase commit is not supported in the direct mode. A separate OracleTransaction will be created for every connection in its scope. And these OracleTransaction's work is synchronized not completely - the changes of one OracleTransaction will NOT be visible in other OracleTransactions of current TransactionScope. For example, the 70-th record will be inserted and updated within TransactionScope in the OCI mode, but only inserted (not updated) in the direct mode:
Code: Select all
using (TransactionScope ts = new TransactionScope()) {
    using (OracleConnection connection = new OracleConnection(connStr)) {
        connection.Open();
        OracleCommand command = connection.CreateCommand();
        command.CommandText = "insert into dept(deptno,dname,loc) values (70,'Development','London')";
        command.ExecuteNonQuery();
    }
    using (OracleConnection connection2 = new OracleConnection(connStr)){
        connection2.Open();
        OracleCommand command2 = connection2.CreateCommand();
        command2.CommandText = "update dept set loc='New York' where deptno=70";
        command2.ExecuteNonQuery();
    }
    ts.Complete();
}

We are investigating the way to solve this problem in the Direct mode.

Concerning your sample, there are two workarounds:
1) use the "Pooling=true;" connection string parameter;
2) switch to the OCI mode (via Oracle client) to work with TransactionScope of full value. Be aware that Oracle server doesn't support DDL in a distributed transaction and, as a result, commits transaction forcibly after the first DDL statement. Taking into account this peculiarity, call the myDbContext.Database.Initialize method before creating TransactionScope.
Shalex
Devart Team
 
Posts: 7781
Joined: Thu 14 Aug 2008 12:44

Re: Intermittent Behavior With Multiple DbContexts in MSDTC Transaction

Postby wscalf » Mon 14 Jan 2013 18:41

Wow - that..might actually help. So far, our workaround has been to narrow it down to one DbContext per request/transaction (as arranged by an IoC container), but we recently ran into a problem with that and integration tests that required multiple requests to happen in a batch in a transaction and then be rolled back..

..But these sound like things to try. Thanks. :)

And I'd be interested in any other solutions you come up with as well.
wscalf
 
Posts: 7
Joined: Wed 12 Dec 2012 00:18


Return to dotConnect for Oracle