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
, к сожалению.