Uproszczony zapis using zamyka się wcześniej i nie commituje zmian do bazy

0

Witam.
Natknąłem się na pewien problem, który chyba nie powinien wystąpić. Korzystając z uproszczonego using'a

using var t = db.BeginTransaction();
// ... dużo update'ów
t.Commit();

Nie aktualizuje danych, przechodzi przez całą metodę, nie ma żadnego wyjatku.

Gdy tylko zmieniłem na

using (var t = db.BeginTransaction())
{
  // ... dużo update'ów
  t.Commit();
}

Wszystko działa bez problemów i się aktualizuje. Czy tak powinno być? Coś robię źle?

0

Pokaż cały kod lub wrzuć tu najprostszy kod, który zawiera ten błąd.
Możesz też użyć profilera i zobaczyć, kiedy do bazy danych idzie COMMIT TRANSACTION - ale stawiam dolary przeciw orzechom, że zrobiłeś coś, co nie wykonuje commita, aktualizujesz dane nie w tej bazie, źle zmieniasz dane lub nie wykonujesz SaveChanges.

0

To jest Dapper nie EntityFramework. Baza jest jedna. Różnica w działaniu tylko między uproszczonym using'iem, a "normalnym".

// połączenie do bazy też jest z wykorzystaniem uproszczenia
using var db = _context.CreateConnection();

// w przypadku korzystania z transakcji w Dapperze trzeba otworzyć połączenie do bazy
db.Open();

// otwieram transakcję korzystając z uproszczonego using'a
using var t = db.BeginTransaction();

// ... Aktualizacja dokumentu i jego podobiektów. Masa ifów i foreachów

// Przykłady operacji
if (dokEwidencja.NagEwidencjiVat is not null)
  await db.ExecuteAsync(_queries.UpdateNagEwidencjiVatQuery, dokEwidencja.NagEwidencjiVat, t);

if (dokEwidencja.OpisAnalityczny is not null)
{
  foreach (var opis in dokEwidencja.OpisAnalityczny)
  {
    await db.ExecuteAsync(_queries.UpdateOpisAnalitycznyQuery, opis, t);

    if (opis.Cechy is not null)
    {
      foreach (var feature in opis.Cechy)
      {
        await db.ExecuteAsync(_queries.UpdateFeaturesQuery, feature, t);
      }
    }
  }
}

// na samym końcu
t.Commit();
0

A przejechałeś debuggerem po kodzie? Commit na końcu jest wykonywany? Dane do zmiany są != null?

0

Oczywiście, że przejechałem. Wszystko się wykonuje, nie ma żadnych nulli, bo wszystko jest sprawdzane. Wrzuciłem tutaj uproszczenie, bo tego jest bardzo dużo. Jedyne co może coś nakierować to jak zrobiłem

try { t.Commit(); }
catch(Exception ex) { t.Rollback(); }

To debugger w miejscu gdzie jest t.Rollback(); pokazał IsolationLevel Exception. Tego "błędu" nie ma jeśli zrobie to pełnoprawnie z nawiasami klamrowymi.

0

A ten drugi using to nie powinien mieć czasem { } ?

0

No właśnie z {} działa i comminuje zmiany do bazy, bez nie. Czyli ja nie mogę mieć dwóch takich, uproszczonych using'ów?

0

czyli jak rozwijasz debuggerem t to IsolationLevel rzuca wyjątkiem w przypadku bez klamerek? jaki wyjątek?

0

Tak to wygląda.

isolation_error.png

3

To nie jest żaden wyjątek, po prostu te property w obecnym stanie obiektu nie jest obsługiwane. To normalne, że jak przeglądasz listę właściwości obiektu, to połowa z nich rzuca wyjątkami tak, jak na Twoim screenshocie.

0

A nie jest tak, że wykonujesz Commit() poza scopem usinga z transakcją? Wewnętrzny using wykona dispose jako pierwszy i potem dopiero zostanie wywołany dispose na zewnętrznym usingu?

Dawno nie siedziałem w internalach C# ale bym sprawdził jak te dwa kody (jeden z klamrami, drugi bez) wygląda w IL. Może być tak, że kompilator parsując kod i generując z niego IL robi jakiś trick pod spodem który sprawia, że wywołanie commita jest poza zakresem? Najlepiej tak jak napisałem - sprawdzić to u źródła w IL. Ewentualnie potem zdekompilować do C# za pomocą ILSpy albo dotPeeka.

Zawsze byłem nieufny do takich lukrów składniowych, które robią jakieś czary pod spodem tak jak ostatnio to jest modne w ASP.NET Core :) Nie ma to jak klasyczna struktura programu czyli namespace potem class potem metody, pola...

0

A to nie jest tak jak z if-em bez klamerek? Na szybko przeleciałem po przykładach i wszędzie widzę że wewnętrzny using ma zawsze klamry tak jak tu żeby wykonać nie jedną instrukcję ale blok instrukcji.

using (var resp = req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var reader = new StreamReader(stream))
{
    TextBox1.Text = reader.ReadToEnd(); // or whatever
    ...
}

Więc coś jest na rzeczy z tym jak CLR parsuje te usingi do IL-a.

0

@ŁF: Ok, myślałem, że to ma coś wspólnego z tym, że nie działa commit.

@markone_dev: No właśnie wszystko by na to wskazywało, że w miejscu t.Commit() scope z using'a nie istnieje i nie ma jak transakcji zapisać. Pewnie debugowaniem nie da rady tego ogarnąć.

0

@AdamWox:

@markone_dev: No właśnie wszystko by na to wskazywało, że w miejscu t.Commit() scope z using'a nie istnieje i nie ma jak transakcji zapisać. Pewnie debugowaniem nie da rady tego ogarnąć.

Jedynie kompilacja do IL i porównanie outputów.

0

Użyłem dotPeek, bo to akurat miałem zainstalowane i nie dodaje nic po skompilowaniu. Jest identycznie to samo co w kodzie źródłowym - bez klamerek.

1

zmień wersję c# do której dekompiluje do niższej w której nie było jeszcze tego ficzera

0

Wklej cały kod metody który działa i cały kod metody która nie działa (możesz je uprościć, ale sprawdź czy błąd dalej występuje). Na 50% coś pomieszałeś, na 49.9% źle testujesz a na 0.1% dowiemy się czegoś nowego

5

@AdamWox:

screenshot-20230125014016.png

ale pokazujesz to co jest w obiekcie transakcji po jej commicie/rollbacku, no to raczej nie tędy droga, zobacz co tam jest przed robieniem commita.

dodatkowo jeżeli niby debugger nie pomaga, to sobie dodaj debug printki (czy te pętle się faktycznie wykonują) i zobacz logi SQL Servera.

U mnie działa

using var db = new SqlConnection(cs);
db.Open();
using var t = db.BeginTransaction();

if (DateTime.Now.Year >= 2023)
{
    var dp = new DynamicParameters();
    dp.Add("@B", "W");
    dp.Add("@C", "E");
    await db.ExecuteAsync("INSERT INTO TestTable VALUES (@B)", dp, t);
    await db.ExecuteAsync("INSERT INTO TestTable VALUES (@C)", dp, t);
}

t.Commit();

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