Всем привет!
Реализовал данный шаблон для Asp.Net.
Юнит создается на каждый скоуп.
public bool Commit()
{
try
{
Transaction.Commit();
return true; // - достаточно ли?
}
catch (Exception exc)
{
Logger.LogException(exc);
Transaction.Rollback();
// ??? нужен ли здесь throw; или возвращать, например, Result = Ok | Exception?
}
finally
{
Transaction.Dispose();
ResetRepositories();// обнуляем ссылки на репозитории(с целью повторного использования
Transaction = Connection.BeginTransaction(); // в новой транзакции
}
return false;
}
Достаточно ли возвращать true в случае коммита или нужно выбрасывать исключение после отката транзакции?
И нужен ли публичный метод отката
[прскипано]
AA>Достаточно ли возвращать true в случае коммита или нужно выбрасывать исключение после отката транзакции? AA>И нужен ли публичный метод отката
public void Rollback
и почему если да?
Я вижу бредовый неполный кусок кода, нужен ли он? Без малейшего понятия.
Не придумывайте себе на голову свои фреймворки, антипаттерны — вижу generic repository уже освоен.
Подумайте что вас сподвигло на написание сего чуда, реальная потребность или хотелка чтоб UoW был.
Очень познавательно, что такая хрень есть в ASP.NET Boilerplate, примечательно что на каждый вызов ихнего сервиса стартуется транзакция по умолчанию. Это еще надо знать как отрубить!
Здравствуйте, Danchik, Вы писали:
D>Здравствуйте, varenikAA, Вы писали:
D>[прскипано]
AA>>Достаточно ли возвращать true в случае коммита или нужно выбрасывать исключение после отката транзакции? AA>>И нужен ли публичный метод отката
public void Rollback
и почему если да?
D>Я вижу бредовый неполный кусок кода, нужен ли он? Без малейшего понятия. D>Не придумывайте себе на голову свои фреймворки, атипатерны — вижу generic repository уже освоен.
D>Подумайте что вас сподвигло на написание сего чуда, реальная потребность или хотелка чтоб UoW был. D>Очень познавательно, что такая хрень есть в ASP.NET Boilerplate, примечательно что на каждый вызов ихнего сервиса стартуется транзакция по умолчанию. Это еще надо знать как отрубить!
Спасибо, за ответ, ну в асп-нете же жизненным циклом объектов управляет Microsoft.Extensions.DependencyInjection,
юнит создается на скоуп, как только скоуп покинули, коннекш закрывается автоматом(в диспозе).
И как иначе, если параллельные транзакции в большинстве субд запрещены?
Здравствуйте, varenikAA, Вы писали:
AA>Здравствуйте, Danchik, Вы писали:
D>>Я вижу бредовый неполный кусок кода, нужен ли он? Без малейшего понятия. D>>Не придумывайте себе на голову свои фреймворки, атипатерны — вижу generic repository уже освоен.
AA>Спасибо, за ответ, ну в асп-нете же жизненным циклом объектов управляет Microsoft.Extensions.DependencyInjection, AA> юнит создается на скоуп, как только скоуп покинули, коннекш закрывается автоматом(в диспозе). AA>И как иначе, если параллельные транзакции в большинстве субд запрещены?
Тоисть на каждый вызов метода контроллера будем стартовать транзакцию или как-то по другому использовать?
Неявный старт транзакции вам потом боком вылезет.
Как вам такой вариант?
using (var tran = GetTransactionScope(db))
{
/// bla, bla
tran.Commit();
}
Если Commit не вызывался, значит было исключение. В таком случае на Dispose делаем Rollback, иначе ничего не делаем.
Если все правильно написать можно, например сделать TransactionManager, пускай считает вложенность чтобы методы его использовали по DI
public class TransactionManager : IDisposable
{
private readonly DbContext _db;
private int _nesting;
private IDbContextTransaction _transaction;
public TransactionManager(DbContext db)
{
_db = db;
}
public void Dispose()
{
_transaction?.Dispose();
}
public TransactionScope BeginTransaction()
{
if (_nesting == 0)
_transaction = _db.Database.BeginTransaction();
var scope = new TransactionScope(this);
++_nesting;
return scope;
}
private void RollbackInternal()
{
if (_transaction != null)
{
_transaction.Rollback();
_transaction.Dispose();
_transaction = null;
}
}
private void LeaveInternal()
{
if (--_nesting < 0)
throw new Exception("Invalid nesting.");
}
private void CommitInternal()
{
if (_transaction == null)
throw new Exception("Transaction not stared. Possible you have forgot to call Commit() in nested scope.");
if (_nesting == 1)
_transaction.Commit();
}
public class TransactionScope : IDisposable
{
private readonly TransactionManager _tm;
private bool _committed;
internal TransactionScope(TransactionManager tm)
{
_tm = tm;
}
public void Dispose()
{
if (!_committed)
{
// rollback ASAP
_tm.RollbackInternal();
}
_tm.LeaveInternal();
}
public void Commit()
{
_tm.CommitInternal();
_committed = true;
}
}
}
....
using (var tran1 = tm.BeginTransaction())
{
using (var tran2 = tm.BeginTransaction())
{
using (var tran3 = tm.BeginTransaction())
{
throw new Exception("Something happened.");
tran3.Commit();
}
tran2.Commit();
}
tran1.Commit();
}
Думал приблизительно напишу, но вроде как рабочее решение
D>Очень познавательно, что такая хрень есть в ASP.NET Boilerplate, примечательно что на каждый вызов ихнего сервиса стартуется транзакция по умолчанию. Это еще надо знать как отрубить!
Здравствуйте, vorona, Вы писали:
D>>Очень познавательно, что такая хрень есть в ASP.NET Boilerplate, примечательно что на каждый вызов ихнего сервиса стартуется транзакция по умолчанию. Это еще надо знать как отрубить!
V>Где можно прочитать про это?
Я не советую делать вложенные транзакции, и запретил бы их поскольку сильно запутывает логику на уровне БД. Так как непонятно становится, что такое откат материнской транзакции при закомиченной внутренней и как такое реализовать на уровне БД Если используется EF, где контекст является UnityOfWork, то возникает проблема передачи объектов из контекстов в вложенные контексты. В общем, лучше разрешить параллельные транзакции, но запретить вложенные.
update поправлен русский язык и убран т9
Здравствуйте, GlebZ, Вы писали:
GZ>Здравствуйте, Danchik, Вы писали:
GZ>Я не советую делать вложенные транзакции, и запретил бы их поскольку сильно запутывает логику на уровне БД. Так как непонятно становится, что такое откат материнской транзакции при закомиченной внутренней и как такое реализовать на уровне БД Если используется EF, где контекст является UnityOfWork, то возникает проблема передачи объектов из контекстов в вложенные контексты. В общем, лучше разрешить параллельные транзакции, но запретить вложенные. GZ>update поправлен русский язык и убран т9
Ниче не понял, я что сделал вложенные транзакции? Я сделал эмуляцию вложенности.
Представим себе три сервиса Service1, Service2, Service3. Каждый из них будет делать какую-то работу, сам или через другой сервис.
class Service1
{
public Service1(DbContext ctx, TransactionManager tm, Service1 service1, Service2 service2) ...
public DoSome()
{
using (var tran = _tm.BeginTransaction())
{
_service2.DoSome();
_service3.DoSome();
tran.Commit();
}
}
}
class Service2
{
public Service1(DbContext ctx, TransactionManager tm) ...
public DoSome()
{
using (var tran = _tm.BeginTransaction())
{
// this service needs to be run in transaction
// magic with EF objects
ctx.SaveChanges()
tran.Commit();
}
}
}
class Service3
{
public Service1(DbContext ctx) ...
public DoSome()
{
// magic with EF objects, no transaction needed
ctx.SaveChanges();
}
}
Если вы дернули Service2.DoWork() напрямую — он уйдет в транзакции
А вот если мы дернем Service1.DoWork(), мы будем уверены что транзакция стартанется там где надо и комит произойдет в нужном месте.
Я убрал неявный вызов транзакций там где они не нужны. Все равно это не идеальное решение, к примеру если Service2 попробует ломануться в удаленный ресурс, думая что он не в транзакции.
Ну и для удобства SaveChanges можно пихануть в Commit()
Здравствуйте, Danchik, Вы писали:
D>Если вы дернули Service2.DoWork() напрямую — он уйдет в транзакции D>А вот если мы дернем Service1.DoWork(), мы будем уверены что транзакция стартанется там где надо и комит произойдет в нужном месте.
Я понял тебя. У тебя именно EF вариант работы с БД, что уже является реалзацией UoF. В силу этого возникает вопрос — а зачем это надо?
В DbContext у тебя уже есть фаза накопления изменений и фаза сброса изменений (SaveChanges). И получается что каждый твой сервис должен проголосовать за сохранение, и уже реально запускающая транзакция сохранит если все проголосовали. А возврат ошибки не является ошибкой. Для этого тебе пришлось опускать два контекcта, контекст голосовалки и контекст EF. Если не использовать данный механизм, то получается что успех для сервиса будет определяться возвратом без ошибки, и контекст нужен один. А учитывая что обычно вызывающий код это начало какого-то сценария, содержит функции сериализаций результатов, логирование успехов и неуспехов, и должны быть надежно защищена от ошибок внутренних сервисов, то проще организационно ограничить количество запускающих транзакций в каком-то слое, а не размазывать такую логику по всему приложению. Создание же транзакций по прицнипе на всякий случай ведет к сайд эффектам.
D>Я убрал неявный вызов транзакций там где они не нужны. Все равно это не идеальное решение, к примеру если Service2 попробует ломануться в удаленный ресурс, думая что он не в транзакции.
Ага. И это проблема шире — это проблема проноса транзакций через DI. Всегда нужно делать параллельные транзакции, где-то в протокол что-то записать, где-то зафиксировать, пока что-то выкачивается. И получается что параллельность транзакций важнее, чем их вложенность. Это нужно строить свои Scope с своими DbContext.
D>Ну и для удобства SaveChanges можно пихануть в Commit()
Не для удобства а потому что иначе работать не будет.
[skip]
D>>Ну и для удобства SaveChanges можно пихануть в Commit() GZ>Не для удобства а потому что иначе работать не будет.
Да то такое, EF много минингита придумал при работе с базой данных, много и убрал. Но стартовать транзакцию на каждый чих, это конкретный перебор. Дай вам счастья не столкнуться с огромной базой в которой сие тормознет всех и вся.
Я дал решение для DI фонатов и UoW адептов, использовать или нет — их дело. Я просто обьяву разместил