TransactionScope причинява блокиране?

Пиша някои Unit тестове срещу база данни и използваме транзакции, за да сме сигурни, че нашите тестови данни ще бъдат премахнати в края.

Сблъсквам се с проблем, при който методите, които тествам, използват свои собствени обекти TransactionScope и изглежда, че блокират при натискане на базата данни.

Това е в основния клас на моя тест:

BaseScope = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUnCommitted, Timeout = new System.TimeSpan(0, 5, 0) });

и след това вътре в метода, който тествам, той прави:

using (TransactionScope scope = new TransactionScope())

Първият път, когато кодът във втория обхват докосне базата данни, той увисва. Имам ли начин да заобиколя този проблем?


person Jonas    schedule 07.10.2010    source източник


Отговори (3)


Ако използвате база данни, значи не правите модулно тестване и проблемите, които срещате, са една от причините истинското модулно тестване да използва Mocks и Stubs.

Сега тестовете, които правите, са много ценни и в някои случаи всъщност бих ги направил вместо Unit Testing. Означавам това тестване за ранна интеграция (EIT). Ключовият момент тук е, че откриваме цял нов клас грешки, когато работим с истинското нещо, а не с макетите на Unit Test. И ключът тук е истинското нещо. Веднага след като фалшифицирате средата с изкуствени обхвати на транзакциите и т.н., вие губите голяма част от ползата от EIT, защото не улавяте фини грешки при взаимодействието или (както във вашия случай) въвеждате изкуствени проблеми.

Бих намерил начин бързо да попълня базата данни с достатъчно тестови данни и да я възстановя до това състояние извън теста. Скриптът за "нулиране до известно състояние" е много полезен за този вид тестове.

person djna    schedule 07.10.2010
comment
Мисля, че това е вариантът, на който ще се спра. Написах някакъв код за създаване на нова/празна база данни, която се изпълнява в началото на тестовете, а в края базата данни се изтрива. - person Jonas; 08.10.2010

Когато вложите TransactionScope екземпляри, можете да завършите с разпределена транзакция, а не с проста локална транзакция. Това поведение варира донякъде в зависимост от използваната база данни. SQLServer 2008, например, не ескалира до DTX, освен ако действително не са включени множество бази данни. Oracle, от друга страна, винаги ще ескалира до разпределена транзакция, тъй като не може да не поддържа връзки за споделяне за една локална транзакция.

В зависимост от това коя база данни и какво TransactionScopeOption използвате, може да стигнете до блокиране. Това се случва, защото DTX често изискват заключвания на таблици, за да се гарантира, че могат да бъдат ангажирани атомарно. В Oracle, например, ако стартирате DTX и се сринете или загубите връзката си, преди да сте го завършили, можете да завършите с „Под съмнение разпределена транзакция“. Тази „Под съмнение“ транзакция може да заключи една или повече таблици, като попречи на друга сесия да ги модифицира, докато DBA не изпълни команда ROLLBACK FORCE върху ИД на чакащата транзакция. Някои бази данни (като SQLServer) се опитват да открият такива блокирания и да прекратят една от нарушаващите транзакции ... но това е гарантирано да се случи.

Предлагам ви един от двата варианта:

  1. Решете дали наистина трябва да пишете тестове, които засягат базата данни. Често пъти можете да използвате макет или мъниче, за да избегнете необходимостта да пишете тестове, които променят и след това връщат назад базата данни. Избягването на подобни проблеми има смисъл, тъй като едновременно ускорява вашите тестове и елиминира потенциалната зависимост от тях. Понякога обаче не можете да направите това.
  2. Ако наистина трябва да тествате логиката си спрямо базата данни, помислете дали да не модифицирате кода си, така че всички методи да използват една и съща връзка с база данни, за да изпълнят своя SQL. Това ще елиминира създаването на разпределена транзакция и, надяваме се, ще преодолее проблема ви.

Може също да искате да разгледате изгледа на изчакващите транзакции на вашата база данни (в Oracle се нарича PENDING_TRANS$ ... в SQLServer има функция XACT_STATE()).

person LBushkin    schedule 07.10.2010

Ще трябва да извършите основната си транзакция, за да деблокирате вашия тестов метод, което предполагам, че не е поведението, което желаете. Трябва да накарате транзакцията на вашия тестов метод да се присъедини към транзакцията на външната среда (чадър/родител/база/външна), създадена във вашия базов клас, вместо да се опитвате да създадете своя собствена.

От MSDN CommittableTransactionClass Забележки (акцентът е мой):

Препоръчително е да създавате имплицитни транзакции с помощта на класа TransactionScope, така че контекстът на заобикалящата транзакция да се управлява автоматично вместо вас. Трябва също да използвате класа TransactionScope и DependentTransaction за приложения, които изискват използването на една и съща транзакция в множество извиквания на функции или множество извиквания на нишки. За повече информация относно този модел вижте Имплементиране на имплицитна транзакция чрез обхват на транзакция.

Създаването на CommittableTransaction не задава автоматично заобикалящата транзакция, която е транзакцията, в която се изпълнява вашият код. Можете да получите или зададете заобикалящата транзакция, като извикате статичното свойство Current на глобалния обект Transaction. За повече информация относно заобикалящите транзакции, вижте раздела Управление на потока на транзакция с помощта на TransactionScopeOption на темата „Имплементиране на имплицитна транзакция с помощта на обхват на транзакция“. Ако заобикалящата транзакция не е зададена, всяка операция на мениджър на ресурси не е част от тази транзакция. Трябва изрично да зададете и нулирате заобикалящата транзакция, за да гарантирате, че мениджърите на ресурси работят в правилния контекст на транзакция.

Докато не бъде ангажирана CommittableTransaction, всички ресурси, включени в транзакцията, са все още заключени.

Както djna посочи, използването на транзакции за връщане назад на промените, направени по време на тестване, е доста злоупотреба. Вие трябва да сте добър гражданин и да отмените и промените, които той прави в базата данни в клауза finally, така че други тестове, които могат да се изпълняват след него, никога да не бъдат засегнати. Ако имате много тестове, които вече не се държат добре, вероятно няма да тръгнете по този път сега. В такъв случай променете базата си, за да използвате имплицитни транзакции с обхват, зададен на RequiresNew, а във вашия тестов метод използвайте Required.

person Mike Atlas    schedule 07.10.2010
comment
Искам само да отбележа, че моят вот против това е напълно несвързан и изглежда е бил глас за отмъщение, тъй като получих поредица от 5 гласа против едновременно на по-стари отговори :/ - person Mike Atlas; 31.10.2010
comment
Благодаря, Дамян :) Сигурен съм, че отговорът ми е дяволски добър. - person Mike Atlas; 04.12.2010