Pergunta

Eu tenho um aplicativo VB6 que eu estou convertendo para .net. Estou fazendo isso em fases para que os clientes terão tanto VB6 e aplicações .net ao mesmo. Parte dos caches aplicativo ADO 2.8 de registros COM para uma tabela no SQL Server e recupera-los quando necessário. Os usos aplicativo .NET que persistiu mesmos conjuntos de registos. Eu tenho c # código que recupera o conjunto de registros e converte-persistiu a um conjunto de dados. A minha pergunta é -? Am I fazê-lo da maneira mais eficiente

Este é meu código que recupera o conjunto de registros do banco de dados -

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

Este é o código que converte o fluxo ADO para um conjuntos de dados -

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

Obrigado a todos

EDIT: Eu adicionei uma recompensa para ver se alguém pode tornar o código mais eficiente

.
Foi útil?

Solução

De um modo geral, você não está aproveitando bastante a instrução usando e manipulação de tudo sozinho. Infelizmente, você está fazendo isso da maneira errada, no que se você tiver uma implementação de IDisposable que lança uma exceção na chamada para Dispose, as outras chamadas de dispor não ter lugar. Se você usar a instrução usando, todas as implementações de IDisposable.Dispose será chamado, não importa como eles são aninhados.

Deixe-nos ir através do LoadTextFromDBToADODBStream primeiro. O maciça questão aqui é que você está compartilhando uma conexão quando você não deve ser. Você deve criar a ligação para o seu funcionamento, usando-o, em seguida, fechá-lo para baixo. Isso não é o caso aqui.

Então, vamos supor que você criar a sua ligação em um método separado, como este:

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

Você também vai precisar a seguinte estrutura para gerir adequadamente as suas referências 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);
    }
}

Você deseja gerenciar suas referências COM com isso para que você liberá-los quando você está feito com eles, não através de coleta de lixo. COM confia na finalização determinística, e você não consegue ignorar que só porque você está em .NET. A estrutura acima aproveita IDisposable (eo fato de que é uma estrutura e as nuances que vêm com ele) para ajudar a fazê-lo de uma forma determinista.

O T tipo de parâmetro será o tipo de classe que é criado para interoperabilidade, no caso do fluxo, será ADODB.StreamClass.

Seu LoadTextFromDBToADODBStream então fica assim:

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

Notas:

  1. Este é o lugar onde você deseja criar sua conexão. Você quer usá-lo em uma instrução usando, porque você quer ter certeza de que seus recursos são limpos, em caso de sucesso ou fracasso.
  2. É mais fácil de curto-circuito o código aqui e retornar uma nova instância de ComReference<StreamClass>. Ao criar este, tem o efeito de devolver uma estrutura que não tem referência (que é o que você quer, ao invés de chamar o estático Create método).
  3. Na chamando o estático Create método, você está criando a nova instância do ADODB.StreamClass. Você quer ter certeza de que, se algo der errado, este é descartado após a libertação (uma vez que é uma implementação da interface COM, e dependente de finalização determinística).
  4. Não há necessidade de criar um novo UnicodeEncoding. Você pode apenas usar a propriedade Unicode na classe Encoding para usar uma instância premade.
  5. Em chamar release, você define o campo de referência para nulo na instância que está na pilha atual, e transferi-lo para o ComReference<StreamClass> que é retornado. Desta forma, a referência StreamClass ainda está vivo, e quando Dispose é chamado na variável de pilha, ele não passar essa referência a ReleaseComObject.

Passando para o código que chama 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. Este vai receber o valor de retorno da chamada para lançamento em LoadTextFromDBToADODBStream. Ele irá conter a referência ao vivo para o ADODB.Stream criado lá, e a instrução using garante que é limpo quando o escopo é esquerda.
  2. Como antes, isso torna mais fácil para fazer referência a referência direta, em vez de sempre ter que chamar adoStreamClass.Reference.<method>
  3. Usando outro ComReference, desta vez ComReference<RecordsetClass>.
  4. Deixe o compilador fazer o trabalho sujo para você.

Ao usar a instrução usando mais, você pode limpar um monte de código que foi tornando-se muito difícil de ler. Além disso, em geral, você estava limpando algumas questões de recursos que surgiram em face de exceção, assim como implementações COM manipulados que não estavam sendo descartados corretamente.

Outras dicas

Se você está dizendo que todo o conjunto de registros COM está sendo mantido para uma única coluna na tabela de banco de dados como um binário objeto (matriz de bytes), então eu não vejo nenhuma maneira em torno da complexidade. Você tem que converter a matriz de bytes para o mesmo objeto concreto que foi serializado de (a COM registros), antes que você pode manipulá-lo.

Não responder a sua pergunta específica .... mas desde que você está especificando a eficiência, presumo que você está querendo velocidade.
Será que você pensa sobre a persistência-lo duas vezes, tanto como ADO e ADO.Net, então, o solicitante pode recuperar o mais adequado e ignorar a conversão de tempo de execução (isto é assumindo muitos mais leituras do que escreve). Para um impulso extra, talvez armazenar n número de conjuntos de dados na memória, onde podem ser devolvidos imediatamente ao invés de ser recarregado a partir do db. Novamente, isso só seria útil dependendo de seus dados e pedidos específicos.

O código é bastante eficiente em geral. Embora concorde com a necessidade de ter manipulação COM generalizada rotinas (apenas para a consistência, se nada mais), eu não sei o desempenho extra que você vai torcer de sua recomendação de não reutilizar a conexão db.

A única coisa que eu preocupar é que você está usando ADO Stream. Este objeto em particular tem um efeito colateral pequeno de comer memória como uma criança numa loja de doces. Eu confira este artigo ( http://www.vbrad.com/article. aspx? id = 12 ) e certifique-se de que seu código não tem esse problema.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top