2013-05-22 2 views
8

У меня есть следующий очень простой модульный тест, который воспроизводит случай, когда 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(); 
      } 
     } 
    } 
} 
+0

Это может быть "грязное чтение", в зависимости от используемой базы данных и на уровне изоляции. SQL Server, например, имеет уровень изоляции 'READ UNCOMMITTED' (http://msdn.microsoft.com/en-us/library/ms173763(v=sql.100).aspx), который позволяет потоку читать данные, вставленные другим нить в транзакции до ее совершения. Данные «грязные» в том смысле, что они могут «исчезнуть» из базы данных, когда второй поток решает отменить транзакцию. Но 'READ UNCOMMITTED' не является значением по умолчанию в SQL Server. – Slauma

+0

@Slauma Если он использует SQL Server с пулом соединений, возможно, он получит соединение, которое [наследует ранее установленный уровень изоляции] (http://support.microsoft.com/kb/972915). @OhadMeir. Вы можете попробовать обернуть свои операции в «TransactionScope» с явно установленным уровнем изоляции «IsolationLevel.ReadCommitted» и посмотреть, продолжает ли ошибка. –

+0

добавлено IsolationLevel.ReadCommitted - тест по-прежнему не удается –

ответ

6

Попробуйте базу данных опция «Чтение записанного моментального снимка» = «Истина».

У нас были такие же проблемы. Этот вариант решил их.

Дополнительная информация о:

http://msdn.microsoft.com/en-us/library/ms173763.aspx

и

Add object and its relationships atomically in SQL Server database

+0

Также добавляется ответ, чтобы показать, как его решить в модульном тесте –