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
}