У меня есть следующий очень простой модульный тест, который воспроизводит случай, когда DbContext.SaveChanges не является атомарным. Не атомарным я имею в виду, что зафиксированные данные могут быть прочитаны до завершения коммита.Код элемента Entity Framework First: SaveChanges не является атомарным
Задача: В цикле добавляется новый TestEntity и ReferencingEntity. Подтвердить задачу: проверяет, есть ли TestEntity, на который не ссылается никакая ссылка ReferenceEntity - это не должно произойти из-за того, как я добавляю сущности.
Неисправность в модульном тесте ... любые советы?
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), который позволяет потоку читать данные, вставленные другим нить в транзакции до ее совершения. Данные «грязные» в том смысле, что они могут «исчезнуть» из базы данных, когда второй поток решает отменить транзакцию. Но 'READ UNCOMMITTED' не является значением по умолчанию в SQL Server. – Slauma
@Slauma Если он использует SQL Server с пулом соединений, возможно, он получит соединение, которое [наследует ранее установленный уровень изоляции] (http://support.microsoft.com/kb/972915). @OhadMeir. Вы можете попробовать обернуть свои операции в «TransactionScope» с явно установленным уровнем изоляции «IsolationLevel.ReadCommitted» и посмотреть, продолжает ли ошибка. –
добавлено IsolationLevel.ReadCommitted - тест по-прежнему не удается –