Преобразовать сохраненный набор записей ADO 2.8 COM в набор данных ADO.Net - PullRequest
4 голосов
/ 03 января 2009

У меня есть приложение VB6, которое я конвертирую в .net. Я делаю это поэтапно, чтобы клиенты одновременно использовали приложения VB6 и .net. Часть приложения кэширует наборы записей COM ADO 2.8 в таблицу в 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;
    }
}

Спасибо всем

РЕДАКТИРОВАТЬ: я добавил награду, чтобы посмотреть, сможет ли кто-нибудь сделать код более эффективным.

Ответы [ 4 ]

4 голосов
/ 14 февраля 2009

Вообще говоря, вы не пользуетесь достаточным преимуществом оператора использования и обрабатываете все это самостоятельно. К сожалению, вы делаете это неправильно, потому что если у вас есть реализация IDisposable, которая генерирует исключение при вызове Dispose, другие вызовы Dispose не выполняются. Если вы используете оператор using, будут вызваны все реализации IDisposable.Dispose, независимо от того, насколько они вложены.

Давайте сначала пройдемся по потоку LoadTextFromDBToADODBS. Проблема массового в том, что вы делитесь соединением, когда этого не следует делать. Вы должны создать соединение для вашей операции, использовать его, а затем закрыть его. Здесь дело не в этом.

Итак, давайте предположим, что вы создаете свое соединение отдельным методом, например так:

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. Здесь вы хотите создать соединение. Вы хотите использовать его в операторе использования, потому что хотите быть уверены, что ваши ресурсы очищены, в случае успеха или неудачи.
  2. Здесь проще замкнуть код и вернуть новый экземпляр ComReference<StreamClass>. Когда вы создаете это, он возвращает структуру, которая не имеет ссылки (что вам нужно, в отличие от вызова статического метода Create).
  3. При вызове статического метода Create вы создаете новый экземпляр ADODB.StreamClass. Вы хотите убедиться, что если что-то пойдет не так, это будет устранено после выпуска (поскольку это реализация интерфейса COM и зависит от детерминированной финализации).
  4. Нет необходимости создавать новую кодировку UnicodeEncoding. Вы можете просто использовать свойство Unicode в классе Encoding для использования готового экземпляра.
  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. Это будет получать возвращаемое значение вызова Release в LoadTextFromDBToADODBStream. Он будет содержать действующую ссылку на созданный там ADODB.Stream, и оператор using будет гарантировать, что он очищен после оставления области действия.
  2. Как и раньше, это облегчает обращение к прямой ссылке, вместо того, чтобы всегда вызывать adoStreamClass.Reference.<method>
  3. Используя другую ссылку, на этот раз ComReference<RecordsetClass>.
  4. Пусть компилятор сделает за вас грязную работу.

Используя больше оператор using, вы можете очистить большую часть кода, который затрудняет чтение. Кроме того, в общем, вы устраняли некоторые проблемы с ресурсами, которые могли возникнуть при исключении, а также обрабатывали реализации COM, которые не были корректно утилизированы.

0 голосов
/ 16 февраля 2009

Код выглядит довольно эффективно в целом. Хотя я согласен с необходимостью иметь обобщенные процедуры обработки COM (просто для согласованности, если не для чего-то еще), я не знаю, какую дополнительную производительность вы извлечете из его рекомендации не использовать db-соединение повторно.

Единственное, о чем я бы беспокоился, это о том, что вы используете ADO Stream. Этот конкретный объект имеет небольшой побочный эффект от еды памяти, как ребенок в кондитерской. Я бы проверил эту статью (http://www.vbrad.com/article.aspx?id=12) и убедился, что в вашем коде нет этой проблемы.

0 голосов
/ 13 февраля 2009

Не отвечайте на ваш конкретный вопрос .... но так как вы указываете эффективность, я предполагаю, что вы хотите скорость.
Задумывались ли вы о том, чтобы сохранить его дважды, как в ADO, так и в ADO.Net, тогда запросчик мог получить наиболее подходящее и пропустить преобразование во время выполнения (это предполагает гораздо больше операций чтения, чем записи). Для дополнительного повышения, возможно, сохраните n наборов данных в памяти, где они могут быть возвращены мгновенно, а не перезагружены из базы данных. Опять же, это будет полезно только в зависимости от ваших конкретных данных и запросов.

0 голосов
/ 03 января 2009

Если вы говорите, что весь набор записей COM сохраняется в одном столбце таблицы базы данных в виде двоичного объекта (байтового массива), то я не вижу способа обойти эту сложность. Вы должны преобразовать байтовый массив в тот же конкретный объект, из которого он был сериализован (набор записей COM), прежде чем вы сможете манипулировать им.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...