Hierarchia transakcji w C#

0

Cześć, zastanawiam się czy idzie zrobić w C# "hierarchię" transakcji, która polegałaby na tworzeniu "transakcji dzieci" i ich commitowanie/rollbackowanie. Chodzi mi o takie coś:

var context = new TestDbContext("name=Default");
using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    context.Accounts.Add(new Account { Name = "Top" });
    context.SaveChanges();

    // Pierwsza transakcja "dziecko", która się nie udała ale widzi wsystkie zmiany z górnej transakcji
    try
    {
        using (var child1 = CreateChildTransactionBasedOnParent(scope))
        {
            context.Accounts.Add(new Account { Name = "Child1" });
            context.SaveChanges();

            var all = context.Accounts.ToList(); // all zawiera Account z nazwą Top i Child1

            throw new Exception("Error"); // error powinien spowodować rollback tylko i wyłącznie transakcji 'child1'
        }
    }
    catch (Exception ex) { }

    // Druga transakcja "dziecko", która się uda
    try
    {
        using (var child2 = CreateChildTransactionBasedOnParent(scope))
        {
            context.Accounts.Add(new Account { Name = "Child2" });
            context.SaveChanges();

            var all = context.Accounts.ToList(); // all zawiera Account z nazwą Top i Child2

            child2.Complete()
        }
    }
    catch (Exception ex) { }

    scope.Complete();

Ostatecznie w bazie danych byłyby dwa rekordy z nazwą "Top" i "Child2". Idzie coś takiego osiągnąć?

1

Po pierwsze, jaki realny problem próbujesz rozwiązać?

Po drugie, C# nie ma nic do tego - istotne jest czy ORM i baza, których używasz takie coś wspierają. Jeśli baza nie wspiera, to nigdy tego nie osiągniesz. Jeśli ORM wspiera to trzeba dowiedzieć się jak - bo teoretycznie najbardziej intuicyjne byłoby umieszczenie kolejnych TransactionScope wewnątrz rodzica, ale ORM może też np. udostępniać specjalne API do tego.

0

Ja jestem autorem wątku.
Odnośnie tematu: trafiłem do projektu w którym wykorzystują pewien wewnętrzny "framework", który opiera się na eventach, także każda operacja CRUD jest opakowana w zdarzenia, mamy flow:

void Add(T entity)
{
	using (var scope = new TransactionScope())
	{
		EmitPreAddEvent();
		_context.Set<T>().Add(entity);
		EmitPostAddEvent();
	}
}

I w tych zdarzeniach mogą być wywołane kolejne operacje CRUD.
Weźmy pod uwagę przykładową obsługę zdarzenia (pseudokod):

[Handler(typeof(Account), Event.Add, Stage.Pre)]
void HandlePreAdd(EventContext<Account> context)
{
	try
	{
		AddProductsToAccount(context.Entity);
	}
	catch (Exception ex) { ... }

	DoSomethingForAccount(context.Entity);
}

W chwili obecnej wszystko jest w jednej transakcji, więc jeżeli coś pójdzie nie tak w funkcji ''AddProductsToAccount'', efekty funkcji DoSomethingForAccount (lub jakiejś innej głębszej hierarchii wywołań operacji CRUD) również nie zostaną odzwierciedlone - a chcą mieć taką możliwość. Mam nadzieje, że jasno to opisałem.

Edit: Korzystamy z EF 6 i MS SQL Server.

1

Najprościej byłoby po prostu przekazać jakoś z eventu informację o tym, czy operacja się powiodła, wtedy będzie wiadomo czy wołać Complete na TransactionScope. TransactionScope wewnątrz eventów muszą być utworzone z opcją RequiresNew.

Co nie zmienia faktu, że to będzie tylko bohaterska walka z problemem, który się samemu stworzyło. Albo framework jest zły albo źle używany. To pierwsze jest oczywiste (bo wszystkie wewnętrzne frameworki są z definicji złe), to drugie wynika z tego, że najwyraźniej twórcy frameworka nie wspierają operacji crudowych w eventach, i pewnie nawet mieli ku temu jakieś powody.

0

Ustawienie RequiresNew nie rozwiąże problemu, wręcz przeciwnie powoduje deadlock. Jednym z przypadków biznesowych dla których wykorzystywany jest event PostAdd jest dodania jakiś encji relacyjnych, np. dodanie produktów do nowo utworzonego zamówienia. Pierwszy event (dodanie zamówienia) powoduje stworzenie nowej transakcji, następnie dodawanie produktów są również w nowej transakcji, przez co ta druga transakcja nie widzi jeszcze zamówienia bo nie jest ona zacommitowana. Dlatego szukam jakiegoś mechanizmu "hierarchii" transakcji.

Cóż nic nie poradzę, że framework jest jaki jest, ma być zrobione i tyle. Ja tu tylko zamiatam.

1

A czemu nie można zapisać produktów razem z zamówieniem od razu przy użyciu metody Add? Z tego, co pamiętam, to EF potrafi zapisywać grafy obiektów.

0

To był tylko przykład. Taką architekturę wymyślili sobie kiedyś jacyś programiści. Wykorzystując ten "framework" nie działamy bezpośrednio na EF, nie mamy navigation property i ogólnie pod spodem może być jako źródło danych np. CRM.

Wracając do tematu transakcji doczytałem, że MS SQL Server ma coś takiego jak Nasted Transactions, jednak nie ma dla niego natywnego wsparcia przy pomocy TransactionScope :(

1

Cóż, zagnieżdżanie transakcji nie jest mocną stroną SQL Servera, i nie działa tak jakby się można tego spodziewać, a co za tym idzie wsparcia ze strony EF się nie uświadczy.

Co robić jak żyć?

  1. tak jak somekind sugeruje, zastanowić się nad tym co chcemy osiągnąć i spóbować zmienić logikę tego workflowu bo wydaje się podejrzana,
  2. zobaczyć w jaki sposób rollbackuje się zagnieżdzone transakcje w sql serwerze i spróbować to wpleść w framework, ale wtedy context ef trzeba będzie tworzyc odzielnie za każdym razem
  3. skoro wiemy który event handler się wykłada, to możemy ponowić request i tym razem pominąć te eventhandlery które powodują problem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1