I have a VB6 application that I am converting to .net. I am doing this in phases so clients will have both VB6 and .net applications at the same. Part of the application caches ADO 2.8 COM recordsets to a table in SQL Server and retrieves them as needed. The .net application uses that same persisted recordsets. I have c# code that retrieves the persisted recordset and converts it to a dataset. My question is — Am I doing it in the most efficient manner?
This is my code that retrieves the recordset from the database —
Stream adoStream = null; SqlParameter cmdParameter; SqlCommand cmd = null; SqlDataReader dr = null; string cmdText; int bytesReturned; int chunkSize = 65536; int offSet = 0; UnicodeEncoding readBytes; try { cmdParameter = new SqlParameter(parameterName, idParamter); cmdText = sqlString; cmd = new SqlCommand(); cmd.CommandType = CommandType.Text; cmd.CommandTimeout = 0; cmd.CommandText = cmdText; cmd.Connection = this.pbiSQLConnection; cmd.Parameters.Add(cmdParameter); dr = cmd.ExecuteReader(CommandBehavior.SequentialAccess); dr.Read(); if (dr.HasRows) { readBytes = new UnicodeEncoding(); byte[] byteChunk = new byte[chunkSize]; adoStream = new Stream(); adoStream.Type = StreamTypeEnum.adTypeText; adoStream.Open(Type.Missing, ConnectModeEnum.adModeUnknown, StreamOpenOptionsEnum.adOpenStreamUnspecified, '', ''); do { bytesReturned = (int)dr.GetBytes(0, offSet, byteChunk, 0, chunkSize); size += bytesReturned; if (bytesReturned > 0) { if (bytesReturned < chunkSize) { Array.Resize(ref byteChunk, bytesReturned); } adoStream.WriteText(readBytes.GetString(byteChunk), StreamWriteEnum.stWriteChar); adoStream.Flush(); } offSet += bytesReturned; } while (bytesReturned == chunkSize); } } catch (Exception exLoadResultsFromDB) { throw (exLoadResultsFromDB); } finally { if (dr != null) { if (!dr.IsClosed) { dr.Close(); } dr.Dispose(); } if (cmd != null) { cmd.Dispose(); } }
This is the code that converts the ado stream to a datasets —
adoStream = LoadTextFromDBToADODBStream(resultID, '@result_id', 'some sql statement', ref size); if (adoStream.Size == 0) { success = false; } else { adoStream.Position = 0; DataTable table = new DataTable(); Recordset rs = new Recordset(); rs.Open(adoStream, Type.Missing, CursorTypeEnum.adOpenStatic, LockTypeEnum.adLockBatchOptimistic, -1); if (adoStream != null) { adoStream.Close(); adoStream = null; } source.SourceRows = rs.RecordCount; table.TableName = 'Source'; source.Dataset = new DataSet(); source.Dataset.Tables.Add(table); OleDbDataAdapter adapter = new OleDbDataAdapter(); adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; adapter.Fill(source.Dataset.Tables[0], rs); if (adapter != null) { adapter.Dispose(); adapter = null; } if (adoStream != null) { adoStream.Close(); adoStream = null; } if (rs != null) { if (rs.State == 1) { rs.Close(); } rs = null; } }
Thanks all
EDIT: I added a bounty to see if anyone can make the code more efficient.
Generally speaking, you aren’t taking enough advantage of the using statement and handling it all yourself. Unfortunately, you are doing it the wrong way, in that if you have an implementation of IDisposable which throws an exception on the call to Dispose, the other calls to Dispose do not take place. If you use the using statement, all implementations of IDisposable.Dispose will be called, no matter how nested they are.
Let’s go through the LoadTextFromDBToADODBStream first. The massive issue here is that you are sharing a connection when you shouldn’t be. You should be creating the connection for your operation, using it, then closing it down. That is not the case here.
So let’s assume you create your connection in a separate method, like this:
You are also going to need the following structure to properly manage your COM references:
You want to manage your COM references with this so that you release them when you are done with them, not through garbage collection. COM relies on deterministic finalization, and you don’t get to ignore that just because you are in .NET. The structure above leverages IDisposable (and the fact it is a structure and the nuances that come with it) to help do so in a deterministic way.
The type parameter
Twill be the class type that is created for COM interop, in the case of the stream, it will be ADODB.StreamClass.Your LoadTextFromDBToADODBStream then looks like this:
Notes:
ComReference<StreamClass>. When you create this, it has the effect of returning a structure which has no reference (which is what you want, as opposed to calling the static Create method).ComReference<StreamClass>that is returned. This way, the StreamClass reference is still alive, and when Dispose is called on the stack variable, it doesn’t pass that reference to ReleaseComObject.Moving on to the code that calls LoadTextFromDBToADODBStream:
adoStreamClass.Reference.<method>ComReference<RecordsetClass>.In using the using statement more, you can clean up a lot of the code that was making it very difficult to read. Also, in general, you were cleaning up some resource issues that would have cropped up in the face of exception, as well as handled COM implementations that were not being disposed of correctly.