Форум программистов, компьютерный форум, киберфорум
C#: ASP.NET Core
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.80/15: Рейтинг темы: голосов - 15, средняя оценка - 4.80
1283 / 863 / 257
Регистрация: 08.08.2014
Сообщений: 2,462
1

Управление транзакциями

18.04.2019, 22:10. Показов 3029. Ответов 4
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Вопрос больше по архитектуре, чем по конкретной реализации.

Есть какие-то проверенные практики по управлению транзакциями в рамках определённого контекста выполнения (например, в пределах одного API-запроса)?

К тому, что ниже - я в курсе, что транзакции могут быть не только поверх конкретной базы под ORM, что они могут покрывать сразу несколько БД, могут включать в себя обращения к другим сервисам или очередям. Тут пока интересует самый простой случай, дабы совсем не запутаться.

Например, для стандартного сценария прохождения запроса на API:
1. Контроллер.
2. Сервис БЛ <==> Сервис БЛ <==> Сервис БЛ.
3. Репозиторий-1, Репозиторий-2.
4. ORM.

Ключевой пункт [2], где сервисы могут переиспользовать друг-друга в неожиданном порядке (т.е. они с точки зрения архитектуры все на одном уровне) и каждому из них может требоваться или не требоваться транзакция при общении с [3]->[4].

Из этого сходу напрашивается очевидное решение - регистрировать репозиторий/контекст-ORM со временем жизни 'Scoped', где-то до [2] заранее всегда открывать транзакцию при прохождении запроса вперёд и закрывать/откатывать при возврате результата. Например, в Invoke мидлвара, непосредственно перед вызовом метода контроллера.

Это удобное решение с точки зрения "один раз сделал и забыл", но, имхо, с точки зрения архитектуры кривоватое, т.к. сервисы теряют контроль над частью того функционала, который относится к их области ответственности (их ведь, да?). Т.е. если этот сервис вдруг будет переиспользован в другом API, то тот API тоже будет вынужден знать об особенностях применения транзакций этими сервисами.

Другая мысль - сервис БЛ сам открывает и закрывает транзакции, если ему это необходимо. Однако, в этом случае, если этот сервис вызывает какой-то другой сервис и ему тоже нужна транзакция, то нужно чтобы оба они работали в рамках одной транзакции, и появляется необходимость делать костыль с подсчётом количества открытий/закрытий транзакций и игнорированием лишних открытий и непоследних закрытий. Т.к. если транзакция жива в рамках всего запроса (или цепочки методов сервисов), то повторное её открытие приведёт к ошибке, так же как и преждевременное закрытие.
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
18.04.2019, 22:10
Ответы с готовыми решениями:

Управление транзакциями
Всем доброго времени суток. Пытаюсь добавить в проект управление транзакциями, выскакивает ошибка:...

Управление транзакциями в С++
Нужно реализовать управление транзакциями в с++. А именно работу с 2мя структурами в файле.Вопросы...

Работа с транзакциями
Всем привет! Столкнулся с такой проблемкой: нужно обеспечить проверку данных с формы, и если...

Проблемы с распределёнными транзакциями
Если выполняю SELECT * FROM OPENROWSET('SQLOLEDB','BDC''sa''sa_passwd', 'SELECT * FROM...

4
Эксперт .NET
12504 / 8688 / 1310
Регистрация: 21.01.2016
Сообщений: 32,636
20.04.2019, 10:01 2
Лучший ответ Сообщение было отмечено kotelok как решение

Решение

Цитата Сообщение от kotelok Посмотреть сообщение
сервис БЛ сам открывает и закрывает транзакции, если ему это необходимо.
Именно такое решение.

Цитата Сообщение от kotelok Посмотреть сообщение
Однако, в этом случае, если этот сервис вызывает какой-то другой сервис и ему тоже нужна транзакция, то нужно чтобы оба они работали в рамках одной транзакции, и появляется необходимость делать костыль с подсчётом количества открытий/закрытий транзакций и игнорированием лишних открытий и непоследних закрытий.
А вот это уже фигня) Используйте класс TransactionScope. Он допускает вложенные транзакции. Если сервису хочется открыть транзакцию, то он может это сделать со спокойной совестью. Ему не нужно знать о том, что транзакцию уже открыли. Провайдер ADO.NET тоже знает о TransactionScope (как и все ORM) и о возможности вложения транзакций.

Всё придумано до вас.
1
1283 / 863 / 257
Регистрация: 08.08.2014
Сообщений: 2,462
20.04.2019, 12:59  [ТС] 3
Цитата Сообщение от Usaga Посмотреть сообщение
Используйте класс TransactionScope
Пока не получилось разобраться, как прозрачно встроить его в проект.

Сейчас сделано на простом костыле - LinqToDB.Data.DataConnection регистрируется со временем жизни Scope (т.е. в рамках одного API-запроса), при создании этого объекта он автоматически открывает подключение к БД и удерживает его (это не я, он сам так себя ведёт).

Ну и в реализации DataConnection просто переопределил методы Begin/Commit/Rollback и считаю там количество открытий/закрытий, как описал выше.

Правда, это порождает некоторые неоднозначные ситуации и проблемы:
1. Транзакция открывается на уровне объекта ORM, сервис БЛ к этому объекту доступа не имеет, приходится в репозиториях (наследованием от базового репозитория) выставлять наружу свои методы Begin/Commit/Rollback. Кривовато выглядит.
2. Первый сервис открыл транзакцию, вложенный сервис тоже (по факту ничего не произошло), потом вложенный сервис откатил транзакцию (по факту ничего не произошло), а первый сервис её закоммитил. В итоге изменения вложенного сервиса тоже закоммитились. С другой стороны, это весьма странная ситуация с точки зрения БЛ, т.е. по идее, если вложенный сервис не смог отработать, он должен кинуть исключение и верхний сервис тоже откатит транзакцию.

Добавлено через 34 минуты
Как-то оно странно работает:
1. Сервис-1 создаёт TransactionScope (внутри using). Добавляет запись в таблицу. Вызывает метод Сервис-2.
2. Сервис-2 создаёт TransactionScope (внутри using). Добавляет запись в таблицу. Делает Complete.
3. Сервис-1 получает управление обратно. Далее (для теста) генерирует исключение. Т.е. он не доходит до вызова Complete.
4. Результат: обе записи добавлены в таблицу.

Добавлено через 9 минут
Не подскажете, как правильно использовать TransactionScope в рамках описанной в первом посте задачи и в сочетании с LinqToDb?

Пока что, без TransactionScope, получилось проапгрейдить реализованный выше велосипед до отдельного сервиса с временем жизни Scoped, который имеет методы Begin/Commit/Rollback и знает про все коннекты уровня сессии. Это позволило убрать работу с транзакциями на уровне репозиториев. Теперь, если сервису БЛ нужна транзакция, он обращается к сервису транзакций, а тот уже разруливает вложенность обращений. Дополнительное удобство - т.к. нет необходимости оборачивать запуск транзакции в блок using, методы БЛ выглядят аккуратнее (без лишних отступов).
0
Эксперт .NET
12504 / 8688 / 1310
Регистрация: 21.01.2016
Сообщений: 32,636
20.04.2019, 13:27 4
Цитата Сообщение от kotelok Посмотреть сообщение
Пока не получилось разобраться, как прозрачно встроить его в проект.
C#
1
2
3
4
using(var scope = new TransactionScope())
{
    // код работы с СУБД
}
Добавлено через 1 минуту
Цитата Сообщение от kotelok Посмотреть сообщение
при создании этого объекта он автоматически открывает подключение к БД и удерживает его (это не я, он сам так себя ведёт).
Вы в этом уверены? Подключение должно открываться только на время выполнения запроса и после его выполнения - закрываться.

Добавлено через 1 минуту
Цитата Сообщение от kotelok Посмотреть сообщение
Ну и в реализации DataConnection просто переопределил методы Begin/Commit/Rollback и считаю там количество открытий/закрытий, как описал выше.
Пфффффф. Что тут ещё можно сказать...
1
1283 / 863 / 257
Регистрация: 08.08.2014
Сообщений: 2,462
20.04.2019, 15:56  [ТС] 5
----
C проблемой разобрался, "простыня" новых вопросов не содержит, так что её можно не читать. Решение в последнем абзаце.
----
Цитата Сообщение от Usaga Посмотреть сообщение
Вы в этом уверены? Подключение должно открываться только на время выполнения запроса и после его выполнения - закрываться.
контекст БД

C#
1
2
3
4
    public sealed partial class MainDb : LinqToDB.Data.DataConnection
    {
        public MainDb(string configName) : base(configName)
        {
регистрация
C#
1
2
3
4
5
6
7
8
9
            LinqToDB.Data.DataConnection.AddConfiguration(
                nameof(MainDb), 
                config["MainDb"], 
                new PostgreSQLDataProvider(PostgreSQLVersion.v95));
 
            services.AddScoped((ctx) =>
            {
                return new MainDb(nameof(MainDb));
            });

Когда какой-то сервис запрашивает MainDb, прилетает вызов в лябмду (где регистрация), создаю объект, попадаю в конструктор MainDb и в этот момент свойство Connection инициализировано и находится в состоянии Open. Хотя, вероятно, оно самоинициализируется и открывается при попытке просмотра его в отладке (наверное, можно как-то через рефлекшн это проверить).

Добавлено через 58 секунд
Usaga
C#
1
using(var scope = new TransactionScope())
Вот прямо сходу оно не работает так, как ожидается. Вероятно, нужно как-то дополнительно настроить LinqToDb на учёт этого механизма.

Я выше написал - если внутри вложенного сервиса создаю ещё один TransactionScope, там его коммичу, а в сервисе уровнем выше падаю с исключением до Complete, то в базу коммитятся все изменения - и из внутреннего сервиса, которой закоммитился, и из вышестоящего, который упал.

Добавлено через 7 минут
Да, проверил через reflection, подключение и в самом деле 'null' до первого использования. Инициализируется при просмотре через окно отладки.

Добавлено через 15 минут
И тогда я не понимаю, если LinqToDb каждый раз при выполнении запроса открывает/закрывает соединение, то как TransactionScope убедит его держать одно открытое соединение с активной транзакцией при межсервисных вызовах?

Добавлено через 15 минут
Не убеждает ). Вот этот вызов добавляет запись в БД, хотя tr.Complete() нигде не вызывается:
C#
1
2
3
4
using (var tr = new TransactionScope())
{
    catalogRepository.AddOffice(4, "Office 4");
}
Добавлено через 20 минут
Всё, разобрался. В строке подключения к БД требуется явно задать параметр: Enlist=True;. После этого TransactionScope начинает работать корректно. По крайней мере под Виндой. Надо будет ещё пол Дебианом проверить.
0
20.04.2019, 15:56
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
20.04.2019, 15:56
Помогаю со студенческими работами здесь

Разобраться с транзакциями MySQL+dbExpress
Нужно разобраться как использовать транзакции, теорию почитал, теперь хотелось бы на практике...

Запуск внешних приложений из 1С:Предприятия и работа с транзакциями
Создаю второй пост с кликом о помощи, этот 1С для меня как китайский для, простите, негра :cry: ...

Реализовать алгоритм работы планировщика. Управление виртуальной памятью. Управление файловой системой
Разработка программы менеджера памяти. Свопинг. Сегментная схема организации памяти. Управление...

Движение, вращение, управление движением, управление вращением фигуры
Написать программу, выполняющую четыре операции над графическим...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
5
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru