1

Since I have a "DB util" class with a DataSet QueryDB(string spName, DBInputParams inputParams) method which I use for all my calls to the database, I would like to reuse this method in order to support transacted calls.

So, at the end I will have a SqlDataAdapter.Fill within a SqlTransaction. Will this be a bad practice? Because rarely I see usage of DataAdapter.Fill within a transaction and more often ExecuteReader(). Is there any catch?

Edit1: The thing is that inside my transaction is often needed to retrieve also some data (e.g auto-IDs)... that's why I would like to get it as DataSet.

Edit2: Strange is when I use this approach in a for loop (10000) from 2 different processes, I get "Transaction (Process ID 55) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction." . Is this the right behaviour?

Edit3: (answer for Edit2) I was using IDENT_CURRENT('XTable') which was the source of the error. After I went back to SCOPE_IDENTITY(), everything has been solved.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Learner
  • 3,297
  • 4
  • 37
  • 62

2 Answers2

2

It is a bad practice because while the transaction is open, records/pages/tables that you make changes to are locked for the duration of the transaction. The fill just makes the whole process keep those resources locked longer. Depending on your sql settings, this could block other accesses to those resources.

That said, if it is necessary, it is necessary, just realize the penalty for doing it.

Robert Beaubien
  • 3,126
  • 4
  • 22
  • 29
  • [[The fill just makes the whole process keep those resources locked longer.]] Why? – Learner Jun 09 '11 at 15:59
  • Any changes within a transaction lock that particular resource (table/page/record). If you do a fill in between udpates, the first udpate locks the resource, and has to wait for the fill to happen and wait even longer till you commit or rollback the transaction. Best practice is to line up all your data before creating the transaction, then execute all your changes as fast as possible. – Robert Beaubien Jun 09 '11 at 16:12
  • It seems it doesn't even wait, but throw exception going into deadlock (see my edit2) – Learner Jun 09 '11 at 16:39
2

It is not a bad practice. One thing to remember is that all statements will use an implicit transaction that they will automatically commit when the statement ends. That is a SELECT (as in the SELECT used by Fill) will always use a transaction, the question is whether it will have to start it on itself or it will use the existing one.

Is there any difference between the number, type and duration of locks acquired by a SELECT in an implicit transaction vs. an explicit transaction? Under the default transaction model (READ COMMITTED isolation) NO, there is none. The behavior is identical and indistinguishable. Under other isolation levels (repeatable read, serializable) there is a difference, but that is the necessary difference for the desired higher isolation level to occur and using an explicit transaction is the only way to achieve this desired isolation level, when necessary.

In addition if the SELECT has to read the effects of a transaction that is pending (not yet committed), as in your example (read back the generated IDs) then there is no other way. The SELECT must be part of the transaction that generated the IDs, otherwise it will not be able to see those uncommitted IDs!

A note of caution though. I believe you have at your disposal a great tool that can make all this transaction handling much easier: the System.Transactions. All ADO.Net code is system transaction aware and will automatically enroll any connection and command into the pending transaction, if you simply declare a TransactionScope. That is if function Foo declares a TransactionScope and then calls function Bar, if Bar does any ADO.Net operatio, it will automatically be part of the transaction declared in Foo, even if Bar does nothing explicitly. The TransactionScope is hooked into the thread context and all ADO.Net call called by Bar will check for this context automatically, and use it. Note that I really mean any ADO.Net call, including Oracle provider ones. Alas though there is a warning: using new TransactionScope() Considered Harmful: the default constructor of TransactionScope will create a serializable transaction, which is overkill. You have to use the constructor that takes a TransactionOptions object and change the behavior to ReadCommitted. A second gotcha with TransactionScope is that you have to be very careful how you manage connections: if you open more than one connection under a scope then they will be enrolled in a distributed transaction, which is slow and requires MSDTC to be configured, and leads to all sort of hard to debug errors. But overall I fell that the benefits of using TransactionScope outweight the problems, and the resulted code is always more elegant than passing around IDbTransaction explicitly.

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • I very much appreciate your answer! Well, I am new into TransactionScope, so I should investigate it more. You read my thoughts as well as with Oracle... although their ODP.Net maybe is still problematic with the TransactionScope as I've read through some forums... sooner or later we will need to migrate it to Oracle as well. Do you have any clues about my "edit2"? I did just a simple test program starting a transaction and inside calling 10000 a proc to insert x(fill),get id_x(fill), insert y(fill-including id_x). When I started the app 2 times (this having it in onFormLoad) it threw the ex. – Learner Jun 09 '11 at 17:46
  • Open a new question. Capture the deadlock graphs (http://msdn.microsoft.com/en-us/library/ms190465.aspx) and attach it to the question (as XML, not as picture of the graph!) and also add precise description of your tables *and* indexes. – Remus Rusanu Jun 09 '11 at 17:54
  • Ok, thank you. I'll see if I can reproduce it now, if not... tomorrow. – Learner Jun 09 '11 at 18:18
  • Well, it works OK on my private machine (SQLServer2008) with both ExecuteReader() and Fill() within SqlTransaction... although at work (SQLServer2005) it failed. Other than that I want to thank you convincing me about TransactionScope. Maybe I will go for it in the end... but I need to check it more, to understand it better. THANKS A LOT! You're the master of DB stuff! :) – Learner Jun 09 '11 at 21:40
  • Hi Remus. I have this question: http://stackoverflow.com/questions/6308951/c-usage-of-transactions-in-business-layer-sqlserver-2005-oracle-good-ex . Do you know, by any chance, a good link to solve my problem? Thank you in advance. – Learner Jun 13 '11 at 08:14