Правильный Unit of work
От: varenikAA  
Дата: 21.02.20 02:52
Оценка:
Всем привет!
Реализовал данный шаблон для 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 в случае коммита или нужно выбрасывать исключение после отката транзакции?
И нужен ли публичный метод отката
public void Rollback
и почему если да?
☭ ✊ В мире нет ничего, кроме движущейся материи.
Отредактировано 21.02.2020 3:49 Разраб . Предыдущая версия .
Re: Правильный Unit of work
От: Danchik Украина  
Дата: 21.02.20 05:41
Оценка: 36 (1) +1
Здравствуйте, varenikAA, Вы писали:

[прскипано]

AA>Достаточно ли возвращать true в случае коммита или нужно выбрасывать исключение после отката транзакции?

AA>И нужен ли публичный метод отката
public void Rollback
и почему если да?


Я вижу бредовый неполный кусок кода, нужен ли он? Без малейшего понятия.
Не придумывайте себе на голову свои фреймворки, антипаттерны — вижу generic repository уже освоен.

Подумайте что вас сподвигло на написание сего чуда, реальная потребность или хотелка чтоб UoW был.
Очень познавательно, что такая хрень есть в ASP.NET Boilerplate, примечательно что на каждый вызов ихнего сервиса стартуется транзакция по умолчанию. Это еще надо знать как отрубить!
Отредактировано 25.02.2020 20:17 Danchik . Предыдущая версия .
Re[2]: Правильный Unit of work
От: varenikAA  
Дата: 21.02.20 09:22
Оценка:
Здравствуйте, Danchik, Вы писали:

D>Здравствуйте, varenikAA, Вы писали:


D>[прскипано]


AA>>Достаточно ли возвращать true в случае коммита или нужно выбрасывать исключение после отката транзакции?

AA>>И нужен ли публичный метод отката
public void Rollback
и почему если да?


D>Я вижу бредовый неполный кусок кода, нужен ли он? Без малейшего понятия.

D>Не придумывайте себе на голову свои фреймворки, атипатерны — вижу generic repository уже освоен.

D>Подумайте что вас сподвигло на написание сего чуда, реальная потребность или хотелка чтоб UoW был.

D>Очень познавательно, что такая хрень есть в ASP.NET Boilerplate, примечательно что на каждый вызов ихнего сервиса стартуется транзакция по умолчанию. Это еще надо знать как отрубить!

Спасибо, за ответ, ну в асп-нете же жизненным циклом объектов управляет Microsoft.Extensions.DependencyInjection,
юнит создается на скоуп, как только скоуп покинули, коннекш закрывается автоматом(в диспозе).
И как иначе, если параллельные транзакции в большинстве субд запрещены?
☭ ✊ В мире нет ничего, кроме движущейся материи.
Re[3]: Правильный Unit of work
От: Danchik Украина  
Дата: 21.02.20 12:14
Оценка: 3 (1)
Здравствуйте, 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();
}


Думал приблизительно напишу, но вроде как рабочее решение
Отредактировано 21.02.2020 15:24 Danchik . Предыдущая версия .
Re[4]: Правильный Unit of work
От: varenikAA  
Дата: 22.02.20 03:40
Оценка:
Здравствуйте, Danchik, Вы писали:

D>Думал приблизительно напишу, но вроде как рабочее решение


Спасибо, буду изучать.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Отредактировано 22.02.2020 19:52 AndrewVK . Предыдущая версия .
Re[2]: Правильный Unit of work
От: vorona  
Дата: 22.02.20 09:41
Оценка:
D>Очень познавательно, что такая хрень есть в ASP.NET Boilerplate, примечательно что на каждый вызов ихнего сервиса стартуется транзакция по умолчанию. Это еще надо знать как отрубить!

Где можно прочитать про это?
Re[3]: Правильный Unit of work
От: Danchik Украина  
Дата: 22.02.20 16:43
Оценка:
Здравствуйте, vorona, Вы писали:

D>>Очень познавательно, что такая хрень есть в ASP.NET Boilerplate, примечательно что на каждый вызов ихнего сервиса стартуется транзакция по умолчанию. Это еще надо знать как отрубить!


V>Где можно прочитать про это?


Вот про что конкретно? На всякий пожарный
https://aspnetboilerplate.com/Pages/Documents/Unit-Of-Work
Re[4]: Правильный Unit of work
От: Sharov Россия  
Дата: 26.02.20 11:41
Оценка:
Здравствуйте, Danchik, Вы писали:

D>Думал приблизительно напишу, но вроде как рабочее решение


Я бы синхронизацию добавил в публичные методы...
Кодом людям нужно помогать!
Re[4]: Правильный Unit of work
От: GlebZ Россия  
Дата: 26.02.20 14:50
Оценка:
Здравствуйте, Danchik, Вы писали:

Я не советую делать вложенные транзакции, и запретил бы их поскольку сильно запутывает логику на уровне БД. Так как непонятно становится, что такое откат материнской транзакции при закомиченной внутренней и как такое реализовать на уровне БД Если используется EF, где контекст является UnityOfWork, то возникает проблема передачи объектов из контекстов в вложенные контексты. В общем, лучше разрешить параллельные транзакции, но запретить вложенные.
update поправлен русский язык и убран т9
Отредактировано 26.02.2020 14:53 GlebЗ . Предыдущая версия .
Re[5]: Правильный Unit of work
От: Danchik Украина  
Дата: 26.02.20 16:24
Оценка: +1
Здравствуйте, 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()
Отредактировано 26.02.2020 16:28 Danchik . Предыдущая версия .
Re[6]: Правильный Unit of work
От: GlebZ Россия  
Дата: 27.02.20 16:22
Оценка:
Здравствуйте, Danchik, Вы писали:

D>Если вы дернули Service2.DoWork() напрямую — он уйдет в транзакции

D>А вот если мы дернем Service1.DoWork(), мы будем уверены что транзакция стартанется там где надо и комит произойдет в нужном месте.
Я понял тебя. У тебя именно EF вариант работы с БД, что уже является реалзацией UoF. В силу этого возникает вопрос — а зачем это надо?
В DbContext у тебя уже есть фаза накопления изменений и фаза сброса изменений (SaveChanges). И получается что каждый твой сервис должен проголосовать за сохранение, и уже реально запускающая транзакция сохранит если все проголосовали. А возврат ошибки не является ошибкой. Для этого тебе пришлось опускать два контекcта, контекст голосовалки и контекст EF. Если не использовать данный механизм, то получается что успех для сервиса будет определяться возвратом без ошибки, и контекст нужен один. А учитывая что обычно вызывающий код это начало какого-то сценария, содержит функции сериализаций результатов, логирование успехов и неуспехов, и должны быть надежно защищена от ошибок внутренних сервисов, то проще организационно ограничить количество запускающих транзакций в каком-то слое, а не размазывать такую логику по всему приложению. Создание же транзакций по прицнипе на всякий случай ведет к сайд эффектам.


D>Я убрал неявный вызов транзакций там где они не нужны. Все равно это не идеальное решение, к примеру если Service2 попробует ломануться в удаленный ресурс, думая что он не в транзакции.

Ага. И это проблема шире — это проблема проноса транзакций через DI. Всегда нужно делать параллельные транзакции, где-то в протокол что-то записать, где-то зафиксировать, пока что-то выкачивается. И получается что параллельность транзакций важнее, чем их вложенность. Это нужно строить свои Scope с своими DbContext.

D>Ну и для удобства SaveChanges можно пихануть в Commit()

Не для удобства а потому что иначе работать не будет.
Re[7]: Правильный Unit of work
От: Danchik Украина  
Дата: 27.02.20 19:28
Оценка:
Здравствуйте, GlebZ, Вы писали:

[skip]

D>>Ну и для удобства SaveChanges можно пихануть в Commit()

GZ>Не для удобства а потому что иначе работать не будет.

Да то такое, EF много минингита придумал при работе с базой данных, много и убрал. Но стартовать транзакцию на каждый чих, это конкретный перебор. Дай вам счастья не столкнуться с огромной базой в которой сие тормознет всех и вся.
Я дал решение для DI фонатов и UoW адептов, использовать или нет — их дело. Я просто обьяву разместил
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.