DbContext.SaveChangesがアトミックでない場合を再現する以下の非常に単純な単体テストがあります。 これは、すべてのコミットが完了する前にコミットされたデータを読み取ることができるという意味ではありません。エンティティフレームワークコードファースト:SaveChangesはアトミックではありません
タスクを追加:ループで、新しいTestEntityとReferencingEntityを追加します。 タスクの検証:ReferencingEntityによって参照されていないTestEntityが存在するかどうかをチェックします。これはエンティティを追加する方法のために起こるはずがありません。
ユニットテストに失敗しました...アドバイスはありますか?
EDIT:受け入れ答えによると - InitTest方法で追加提案された解決策でユニットテストを実行するには:
using (var context = new TestContext())
{
var objectContext = (context as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand(string.Format("ALTER DATABASE [{0}] SET READ_COMMITTED_SNAPSHOT ON", context.GetType().FullName));
}
ユニットテスト:
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Atlit.Server.Tests.Integration.SessionProcessing
{
class TestContext : DbContext
{
public DbSet<TestEntity> TestEntities { get; set; }
public DbSet<ReferencingEntity> ReferencingEntities { get; set; }
}
class TestEntity
{
public int TestEntityId { get; set; }
}
class ReferencingEntity
{
public int ReferencingEntityId { get; set; }
public TestEntity TestEntity { get; set; }
}
[TestClass]
public class SaveChangesAtomicTest
{
private volatile int m_Count = 3000;
private volatile bool m_Failed = false;
[TestInitialize]
public void InitTest()
{
using (var context = new TestContext())
{
var dbInitializer = new DropCreateDatabaseAlways<TestContext>();
dbInitializer.InitializeDatabase(context);
}
}
private void AddEntities()
{
while (m_Count-- > 0 && !m_Failed)
{
var transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
using (var transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew, transactionOptions))
{
using (var context = new TestContext())
{
var entity = context.TestEntities.Add(new TestEntity());
context.ReferencingEntities.Add(new ReferencingEntity { TestEntity = entity });
context.SaveChanges();
}
transactionScope.Complete();
}
}
}
private void ValidateEntities()
{
while (m_Count > 0 && !m_Failed)
{
if (FreeEntitiesExist())
{
m_Failed = true;
}
}
}
[TestMethod]
public void TestIsSaveChangesAtomic()
{
var addTask = Task.Factory.StartNew(AddEntities);
var readTask = Task.Factory.StartNew(ValidateEntities);
addTask.Wait();
readTask.Wait();
Assert.IsFalse(FreeEntitiesExist(), "sanity failed");
Assert.IsFalse(m_Failed, "test failed");
}
private static bool FreeEntitiesExist()
{
using (var context = new TestContext())
{
return (from entity in context.TestEntities
where !context.ReferencingEntities.Any(re => re.TestEntity.TestEntityId == entity.TestEntityId)
select entity)
.ToArray().Any();
}
}
}
}
これは、使用しているデータベース上と分離レベルに応じて、 "ダーティリード" である可能性があります。たとえば、SQL Serverには、スレッドが別のスレッドによって挿入されたデータを読み取ることを許可する分離レベルのREAD UNCOMMITTED(http://msdn.microsoft.com/en-us/library/ms173763(v=sql.100).aspx)がありますトランザクションがコミットされる前のトランザクション内のスレッド。データは、第2のスレッドがトランザクションをロールバックすることを決定すると、データベースから「消える」という意味で「ダーティ」です。しかし、「READ UNCOMMITTED」はSQL Serverのデフォルトではありません。 – Slauma
@Slauma接続プールでSQL Serverを使用している場合、[以前に設定した分離レベルを継承する](http://support.microsoft.com/kb/972915)の接続が確立されている可能性があります。 @OhadMeir 'IsolationLevel.ReadCommitted'という明示的に設定された分離レベルを持つ' TransactionScope'で操作をラップして、エラーが継続するかどうかを調べることができます。 –
が追加されましたIsolationLevel.ReadCommitted - テストが失敗しました –