将持久的ADO 2.8 COM记录集转换为ADO.Net DataSet

我有一个VB6应用程序,我转换为.net。 我正在分阶段这样做,所以客户端将同时具有VB6和.net应用程序。 部分应用程序将ADO 2.8 COM记录集缓存到SQL Server中的表,并根据需要检索它们。 .net应用程序使用相同的持久记录集。 我有c#代码检索持久化记录集并将其转换为数据集。 我的问题是 – 我是否以最有效的方式做到了?

这是我从数据库中检索记录集的代码 –

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(); } } 

这是将ado流转换为数据集的代码 –

 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; } } 

谢谢大家

编辑:我添加了一个赏金,看看是否有人可以使代码更有效。

一般来说,您没有充分利用using语句并自己处理它。 不幸的是,你这样做是错误的,因为如果你有一个IDisposable的实现,它会在调用Dispose时抛出exception,那么其他对Dispose的调用就不会发生了。 如果使用using语句,则无论它们是如何嵌套的,都将调用IDisposable.Dispose的所有实现。

我们先来看看LoadTextFromDBToADODBStream。 这里的一个问题是,当你不应该这样做的时候,你正在共享一个连接。 您应该为您的操作创建连接,使用它,然后关闭它。 这里情况不同。

因此,我们假设您使用单独的方法创建连接,如下所示:

 SqlConnection CreateConnection() { // Create the connection here and return it. return ...; } 

您还需要以下结构来正确管理COM引用:

 struct ComReference : IDisposable where T : class, new() { private T reference; public T Reference { get { return reference; } } public static ComReference Create() { // Create the instance. ComReference retVal = new ComReference(); // Set the reference. retVal.reference = new T(); // Return. return retVal; } public ComReference Release() { // Create a copy for return. // Note, this is copied on the stack. ComReference retVal = this; // Set this reference to null; this.reference = null; // Return the reference. return retVal; } public void Dispose() { // If there is a reference, then release. Marshal.ReleaseComObject(reference); } } 

您希望使用此管理COM引用,以便在完成它们时释放它们,而不是通过垃圾回收。 COM依赖于确定性的最终化,你不会因为你在.NET中而忽略它。 上面的结构利用IDisposable(事实上它是一个结构和随之而来的细微差别)以确定的方式帮助这样做。

类型参数T将是为COM互操作创建的类类型,对于流,它将是ADODB.StreamClass。

您的LoadTextFromDBToADODBStream然后如下所示:

 ComReference LoadTextFromDBToADODBStream(int idParameter, string parameterName, string sqlString, ref int size) { int bytesReturned; int chunkSize = 65536; int offSet = 0; // Create the command. using (SqlCommand cmd = new SqlCommand()) { // Set the parameters. cmd.CommandType = CommandType.Text; cmd.CommandTimeout = 0; cmd.CommandText = sqlString; // See (1). using (SqlConnection connection = CreateConnection()) { // Set the connection on the command. cmd.Connection = connection; // Create the parameter and add to the parameters. SqlParameter cmdParameter = new SqlParameter( parameterName, idParameter); cmd.Parameters.Add(cmdParameter); // Create the reader. using (SqlDataReader dr = cmd.ExecuteReader( CommandBehavior.SequentialAccess)) { dr.Read(); // See (2) if (!dr.HasRows) { // Return an empty instance. return new ComReference(); } // Create the stream here. See (3) using (ComReference adoStreamClass = ComReference.Create()) { // Get the stream. StreamClass adoStream = adoStreamClass.Reference; // Open the stream. adoStream.Type = StreamTypeEnum.adTypeText; adoStream.Open(Type.Missing, ConnectModeEnum.adModeUnknown, StreamOpenOptionsEnum.adOpenStreamUnspecified, "", ""); // Create the byte array. byte[] byteChunk = new byte[chunkSize]; // See (4) Encoding readBytes = Encoding.Unicode; // Cycle. 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); // Release the reference and return it. // See (5). return adoStreamClass.Release(); } } } } } 

笔记:

  1. 这是您要创建连接的位置。 您希望在using语句中使用它,因为您希望确保在成功或失败时清理您的资源。
  2. 这里的代码短路并返回ComReference的新实例更容易。 当你创建它时,它具有返回没有引用的结构的效果(这是你想要的,而不是调用静态Create方法)。
  3. 在调用静态Create方法时,您将创建ADODB.StreamClass的新实例。 您希望确保如果出现问题,则会在发布时将其处理掉(因为它是COM接口实现,并且取决于确定性的终结)。
  4. 无需创建新的UnicodeEncoding。 您只需使用Encoding类上的Unicode属性即可使用预制实例。
  5. 在调用release时,在当前堆栈上的实例上将引用字段设置为null,并将其传递给返回的ComReference 。 这样,StreamClass引用仍然存在,并且当在堆栈变量上调用Dispose时,它不会将该引用传递给ReleaseComObject。

继续调用LoadTextFromDBToADODBStream的代码:

 // See (1) using (ComReference adoStreamClass = LoadTextFromDBToADODBStream(resultID, "@result_id", "some sql statement", ref size)) { // Set to the class instance. See (2) StreamClass adoStream = adoStreamClass.Reference; if (adoStream.Size == 0) { success = false; } else { adoStream.Position = 0; DataTable table = new DataTable(); // See (3) using (ComReference rsClass = ComReference.Create()) { Recordset rs = rsClass.Reference; 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); // See (4) using (OleDbDataAdapter adapter = new OleDbDataAdapter()) { adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; adapter.Fill(source.Dataset.Tables[0], rs); } } } } 
  1. 这将在LoadTextFromDBToADODBStream中接收对Release的调用的返回值。 它将包含对在那里创建的ADODB.Stream的实时引用,而using语句将保证在保留范围时清除它。
  2. 和以前一样,这样可以更容易地引用直接引用,而不必总是调用adoStreamClass.Reference.
  3. 使用另一个ComReference,这次是ComReference
  4. 让编译器为你做脏工作。

在使用using语句时,您可以清理许多使其难以阅读的代码。 此外,一般情况下,您正在清理一些在exception情况下会出现的资源问题,以及处理未正确处理的COM实现。

如果您说整个COM记录集作为二进制对象(字节数组)被持久化到数据库表中的单个列,那么我没有看到任何解决方案的复杂性。 在操作之前,必须将字节数组转换为序列化的相同具体对象(COM记录集)。

没有回答你的具体问题……但既然你在指定效率,我认为你想要速度。
您是否考虑过两次持久化,无论是ADO还是ADO.Net,请求者都可以检索最合适的并跳过运行时转换(假设读取次数多于写入次数)。 为了获得额外的提升,可以在内存中存储n个数据集,可以立即返回它们,而不是从数据库重新加载。 同样,这只会根据您的具体数据和请求而有用。

代码看起来非常有效。 虽然我同意需要进行通用的COM处理例程(只是为了保持一致性,如果没有别的),我不知道你会建议不要重用db连接的额外性能。

我唯一担心的是你正在使用ADO Stream。 这个特殊的物体具有像糖果店里的孩子一样吃记忆的小副作用。 我查看了这篇文章( http://www.vbrad.com/article.aspx?id=12 )并确保您的代码没有此问题。