我有以下非常簡單的單元測試,它重現了DbContext.SaveChanges不是原子的情況。 不是原子的,我的意思是可以在所有提交完成之前讀取已提交的數據。Entity Framework代碼優先:SaveChanges不是原子的
添加任務:在一個循環中,添加一個新的TestEntity和一個ReferencingEntity。 驗證任務:檢查是否存在未被任何ReferencingEntity引用的TestEntity--由於添加實體的方式而不應該發生。
單元測試失敗...有什麼建議嗎?
編輯: 根據公認的答案 - 爲了運行提出的方案的單元測試添加在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/zh-cn/library/ms173763(v=sql.100).aspx),它允許線程讀取由另一個插入的數據在提交之前在事務中進行線程化。數據是「髒的」,因爲當第二個線程決定回滾事務時,它們可以從數據庫中「消失」。但'READ UNCOMMITTED'不是SQL Server的默認設置。 – Slauma
@Slauma如果他將SQL Server用於連接池,他可能會獲得[繼承以前設置的隔離級別]的連接(http://support.microsoft.com/kb/972915)。 @OhadMeir你可以嘗試用'IsolationLevel.ReadCommitted'顯式設置的隔離級別在'TransactionScope'中包裝你的操作,看看錯誤是否繼續。 –
增加了IsolationLevel.ReadCommitted - 測試仍然失敗 –