Sunday, February 12, 2012

Aparent DataReader collisions/conflicts between users

This is causing me such grief! Any help would be MUCH appreciated.

Basic summary of the problem:

I have an Intranet site I have developed that uses a SQL Server backend. It uses SqlConnection and SqlDataReader objects to get data form the database.

When more than one person uses the site at the same time, conflicts occur. It seems that the users' processes are attempting to share the SqlDataReaders, which results in problems because one user tries to open the SqlDataReader but it is already open by the other user, and similarly when they try to close a DataReader.

Full details are as follows:

SQL Server 7 backend database

webserver running Windows Windows 2003 server 5.2 SP1

IIS 6.0

.Net version 1.1 1927

Aparent collisions are occuring when more than one person works on the website at the same time.

I am using SQLConnection and SQLDataReader objects for data access from the database, a typical example being:

in Global.asax.vb:

Application("SQLString") = "workstation id={WebserverName};packet size=4096;user id=sa;password={password};data source={SQLServermachine};persist security info=False;initial catalog={DatabaseName}"

in a local class module:

Dim SQLConnection_docstatustable =New SQLConnection(HttpContext.Current.Application("SQLString"))

PublicFunction GetAll()As DocStatuses

'Returns a collection of all the Document Statuses from the database

Dim TheseDocStatusesAsNew DocStatuses

Dim commandAsNew SqlCommand("select * from lkDocStatus where Deleted = 0", SQLConnection_docstatustable)

Dim drAs SqlDataReader

Try

If SQLConnection_docstatustable.State = ConnectionState.ClosedThen SQLConnection_docstatustable.Open()

dr = command.ExecuteReader(CommandBehavior.SingleResult)

While dr.Read()

TheseDocStatuses.Add(PopulateItem(dr))

EndWhile

Finally

IfNot drIsNothingAndAlsoNot dr.IsClosedThen

dr.Close()

EndIf

SQLConnection_docstatustable.Close()

command.Dispose()

EndTry

Return TheseDocStatuses

EndFunction

The above function uses the following function to read each record as it loops through the datareader:

Function PopulateItem(ByVal drAs IDataRecord)As DocStatus

Dim thisDocStatusAsNew DocStatus

thisDocStatus.DocStatusID = Convert.ToInt32(dr("DocStatID"))

IfNot dr("DocStatus")Is DBNull.ValueThen

thisDocStatus.DocStatusString = Convert.ToString(dr("DocStatus"))

EndIf

Return thisDocStatus

EndFunction

This example is a very small one, but all my database queries are done in pretty much the same way. The main difference is that most include a lot more fields.

I have consistently used try/finally blocks, and closed both the SQL Connection and the DataReader in there, so that everything is being tidied up for each specific user.

Depending on precisely what point the two users are at when the problem happens, I get various different .NET error messages, but they all seem to boil down to collisions between the different users, with particular regard to DataReaders.

Some error messages I have received:

Object reference not set to an instance of an object - pointing to adr = command.ExecuteReader(CommandBehavior.SingleResult) statement (I take it to mean the other user's process has just closed this DataReader).

There is already an open DataReader associated with this Connection which must be closed first - pointing to the same place (I take it to mean the other user's process has just opened this DataReader).

ExecuteReader requires an open and available Connection. The connection's current state is closed - pointing to the same place again (I assume the other users's process has just closed the Connection).

Also:

Internal Connection Fatal Error pointing - toSQLConnection_productstable.Close()(I take this also to mean that the other user's process has just closed this Connection).

Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding (I am not sure about this one)

Finally, I get one or two errors that sound like queries may not have not returned all the expected records from the database, perhaps because of a DataReader closing too early.

I understand that .NET 1.1 allows only one datareader per connection, but when I tried turning connection pooling off it made no difference. Nor did allowing a high Min Pool size, although when I tried this I could see the many connections in the Windows Perfomance Manager.

I don't really see why I need to check whether the connection object is closed before opening it, or why I need to check that the datareader is not nothing or closed before closing it, but I found I got worse results still if I didn't do these.

I have tried to replicate this problem on my own computer, but it doesn't happen. This is a standalone PC with SQL Server and webserver all on the same machine. The software is the same, except that I have Windows XP Pro 5.1 SP2, and IIS 5.1 The fact this works OK on mine makes me wonder if there might be a .NET setting different on the two, or perhaps the network might cause problems - their webserver is separate from their SQL one. But the errors I am getting do sound like .NET issues that I could fix by changing the program, if only I could work out what to change!

Regards

David Vaughan

Are any of your data access classes or their functions/properties declared as static or shared?|||I was having issues very similar to yours, and suspected that there was either some sort of cross-contamination of my Connection objects or that the DataReader objects used internally by DataSets were somehow not being closed properly prior to the DataReader's associated Connection being returned to the pool.

Apparently, neither was the case. It's still too early in our testing to tell, but it looks like our actual cuplrits were a handful of procedures that used DataReader objects directly. In each case, the DataReader was either not issued a Close followed by a Dispose, or was not nested inside a try/catch block to do the same on an exception.

So, if I were you, I'd comb through the code once more to see if you have any "bad apples" like this spoiling the bunch. In our case, about 1-2 dozen such procedure calls were to blame, hidden within about 19000 total database calls from managed code over the course of roughly a half hour.|||

misfit815:

Apparently, neither was the case. It's still too early in our testing to tell, but it looks like our actual cuplrits were a handful of procedures that used DataReader objects directly. In each case, the DataReader was either not issued a Close followed by a Dispose, or was not nested inside a try/catch block to do the same on an exception.

I've had the same experience as misfit815. All of my DataReaders are encapsulated using blocks (It's like a Try/Finally block which autodisposes), and I make sure that I don't call other functions that might hit the database inside one of those blocks.

No comments:

Post a Comment