【问题标题】:Convert persisted ADO 2.8 COM recordset to ADO.Net DataSet将持久的 ADO 2.8 COM 记录集转换为 ADO.Net 数据集
【发布时间】:2010-09-29 09:24:31
【问题描述】:

我有一个要转换为 .net 的 VB6 应用程序。我分阶段执行此操作,因此客户端将同时拥有 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;
    }
}

谢谢大家

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

【问题讨论】:

    标签: c# vb6 performance


    【解决方案1】:

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

    让我们先来看看 LoadTextFromDBToADODBStream。这里的大量 问题是您在不应该共享连接的情况下共享连接。您应该为您的操作创建连接,使用它,然后关闭它。这里不是这样。

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

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

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

    struct ComReference<T> : IDisposable where T : class, new()
    {
        private T reference;
    
        public T Reference { get { return reference; } }
    
        public static ComReference<T> Create()
        {
            // Create the instance.
            ComReference<T> retVal = new ComReference<T>();
    
            // Set the reference.
            retVal.reference = new T();
    
            // Return.
            return retVal;
        }
    
        public ComReference<T> Release()
        {
            // Create a copy for return.
            // Note, this is copied on the stack.
            ComReference<T> 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<StreamClass> 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<StreamClass>();
                    }
    
                    // Create the stream here.  See (3)
                    using (ComReference<StreamClass> adoStreamClass =
                        ComReference<StreamClass>.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&lt;StreamClass&gt; 的新实例更容易。当您创建它时,它会返回一个没有引用的结构(这是您想要的,而不是调用静态 Create 方法)。
    3. 在调用静态 Create 方法时,您正在创建 ADODB.StreamClass 的新实例。您要确保如果出现问题,它会在发布时被处理掉(因为它是一个 COM 接口实现,并且依赖于确定性的终结)。
    4. 无需创建新的 UnicodeEncoding。您可以只使用 Encoding 类上的 Unicode 属性来使用预制实例。
    5. 在调用release时,将当前堆栈上的实例的引用字段设置为null,并将其传输到返回的ComReference&lt;StreamClass&gt;。这样,StreamClass 引用仍然有效,并且当对堆栈变量调用 Dispose 时,它​​不会将该引用传递给 ReleaseComObject。

    继续调用 LoadTextFromDBToADODBStream 的代码:

    // See (1)
    using (ComReference<StreamClass> 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<RecordsetClass> rsClass = 
            ComReference<RecordsetClass>.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.&lt;method&gt;
    3. 使用另一个 ComReference,这次是 ComReference&lt;RecordsetClass&gt;
    4. 让编译器为您完成繁琐的工作。

    更多地使用 using 语句,您可以清理很多难以阅读的代码。此外,一般来说,您正在清理一些在遇到异常时会出现的资源问题,以及处理未正确处理的 COM 实现。

    【讨论】:

      【解决方案2】:

      如果您说整个 COM 记录集作为二进制对象(字节数组)被持久保存到数据库表中的单个列,那么我看不出有任何解决复杂性的方法。您必须将字节数组转换为从(COM 记录集)序列化的同一具体对象,然后才能对其进行操作。

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • 我们有一个用 VB6 编写的更新应用程序,必须彻底修改(如果可以的话)以保存到 ADO 和 ADO.Net 数据集或在 .Net 中重写我们也看不到作为备选。此外,我们只在需要时加载一次缓存数据。然后将其存储在缓存服务中。
        【解决方案4】:

        总体而言,代码看起来非常高效。虽然我同意需要通用的 COM 处理例程(只是为了保持一致性,如果没有别的),我不知道你会从他的不重用数据库连接的建议中获得什么额外的性能。

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

        【讨论】:

          猜你喜欢
          • 2012-05-27
          • 2011-01-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-01
          • 1970-01-01
          相关资源
          最近更新 更多