繁体   English   中英

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

[英]Convert persisted ADO 2.8 COM recordset to 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时抛出异常,那么其他对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<StreamClass>的新实例更容易。 当你创建它时,它具有返回没有引用的结构的效果(这是你想要的,而不是调用静态Create方法)。
  3. 在调用静态Create方法时,您将创建ADODB.StreamClass的新实例。 您希望确保在出现问题时将其释放(因为它是COM接口实现,并且取决于确定性的最终化)。
  4. 无需创建新的UnicodeEncoding。 您可以使用Encoding类上的Unicode属性来使用预制实例。
  5. 在调用release时,在当前堆栈上的实例上将引用字段设置为null,并将其传递给返回的ComReference<StreamClass> 这样,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.<method>
  3. 使用另一个ComReference,这次是ComReference<RecordsetClass>
  4. 让编译器为你做脏工作。

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM