Entity Framework: использование транзакций и откатов... Возможно?

Проблема: (с Sql 2005)

  • Как я могу запросить базу данных во время транзакции? (Так как он блокирует таблицу)
  • Как заставить транзакцию откат, а затем закрыть ее, чтобы позволить запрашивать таблицу?

Итак, я нашел это много:

[TestMethod]
public void CreateUser()
{
    TransactionScope transactionScope = new TransactionScope();

    DataContextHandler.Context.AddToForumUser(userToTest);
    DataContextHandler.Context.SaveChanges();

    DataContextHandler.Context.Dispose();
}

Где DataContextHandler - это простой синглтон, который предоставляет объект контекста для моих объектов. Кажется, это работает так, как вы думаете. Он создает пользователя, сохраняет его, а затем откатывается, когда программа заканчивается. (Завершение теста IE)

Проблема: как заставить транзакцию откат и убить себя, чтобы я мог запросить таблицу?

Причина. Для целей тестирования я хочу убедиться, что пользователь:

  • Сохранено
  • Может быть запрошен правильно, чтобы доказать его существование
  • Удаляется (данные нежелательной почты)
  • Можно запросить, чтобы убедиться, что он удален.

Как сейчас, я могу получить транзакцию только после завершения теста, и я не могу понять, как запросить транзакцию вверх:

[TestMethod]
public void CreateUser()
{
    ForumUser userToTest = new ForumUser();

    TransactionScope transactionScope = new TransactionScope();

    DataContextHandler.Context.AddToForumUser(userToTest);
    DataContextHandler.Context.SaveChanges();     

    Assert.IsTrue(userToTest.UserID > 0);

    var foundUser = (from user in DataContextHandler.Context.ForumUser
                    where user.UserID == userToTest.UserID
                    select user).Count();  //KABOOM Can't query since the 
                                           //transaction has the table locked.

    Assert.IsTrue(foundUser == 1);

    DataContextHandler.Context.Dispose();

    var after = (from user in DataContextHandler.Context.ForumUser
                 where user.UserID == userToTest.UserID
                 select user).Count(); //KABOOM Can't query since the 
                                       //transaction has the table locked.

    Assert.IsTrue(after == 0);
}

ОБНОВЛЕНИЕ Это работало для отката и проверки, но все еще не может выполнить запрос в разделе использования:

using(TransactionScope transactionScope = new TransactionScope())
{
    DataContextHandler.Context.AddToForumUser(userToTest);
    DataContextHandler.Context.SaveChanges();
    Assert.IsTrue(userToTest.UserID > 0);
    //Still can't query here.

}

var after = (from user in DataContextHandler.Context.ForumUser
            where user.UserID == userToTest.UserID
            select user).Count();

Assert.IsTrue(after == 0);

Ответ 1

Из MSDN;

"SaveChanges работает в транзакции. SaveChanges откатывает эту транзакцию и генерирует исключение, если какой-либо из грязных объектов ObjectStateEntry не может быть сохранен".

Поэтому кажется, что нет необходимости явно добавлять свою собственную транзакционную обработку через TransactionScope.

Ответ 2

В моем случае я удаляю все записи из таблицы через обычный SQL, поскольку EF не предоставляет для этого функциональности. После этого я добавляю несколько новых объектов, но когда это не удается, таблица не должна быть пустой. Использование MSDTC (TransactionScope) представляется мне невозможным. Я сократил транзакцию до БД:

Мой код:

using (var transaction = context.Connection.BeginTransaction())
{
      // delete all
      base.DeleteAll<TESTEntity>(context);

      // add all
      foreach (var item in items)
      {
           context.TESTEntity.AddObject(item);
      }

      try
      {
           context.SaveChanges();
           transaction.Commit();
           return true;
      }
      catch (Exception ex)
      {
           Logger.Write("Error in Save: " + ex, "Error");
           transaction.Rollback();
           return false;
      }
}

И здесь вспомогательные функции

    protected void DeleteAll<TEntity>(ObjectContext context) where TEntity : class
    {
        string tableName = GetTableName<TEntity>(context);
        int rc = context.ExecuteStoreCommand(string.Format(CultureInfo.InvariantCulture, "DELETE FROM {0}", tableName));
    }

    protected string GetTableName<TEntity>(ObjectContext context) where TEntity : class
    {
        string snippet = "FROM [dbo].[";

        string sql = context.CreateObjectSet<TEntity>().ToTraceString();
        string sqlFirstPart = sql.Substring(sql.IndexOf(snippet) + snippet.Length);
        string tableName = sqlFirstPart.Substring(0, sqlFirstPart.IndexOf("]"));
        return tableName;
    }

Ответ 3

Мне удалось решить очень похожую проблему, используя этот фрагмент кода:

var connection = new EntityConnection("name=MyEntities");
connection.Open();
var tran = connection.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);

try
{
    var dataContext = new MyEntities(connection);

    //CRUD operation1

    //CRUD operation2

    //CRUD operation3 ...

    Assert.AreEqual(expected, actual);
}
catch
{
    throw;
}
finally
{
    tran.Rollback();
    connection.Close();
}

Где MyEntities - EF DataModel. Важнейшей частью является настройка транзакции: System.Data.IsolationLevel.ReadUncommitted.
Используя этот уровень изоляции SQL-запросы могут читать ваши изменения, выполненные внутри транзакции. Также вам необходимо явно создать соединение, как и в первой и второй строках. Я не мог заставить его работать, используя TransactionScope, к сожалению.