Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Método mais rápido para inserções, atualizações e seleções do SQL Server


Esta resposta se concentra principalmente nas operações de 'selecionar' versus atualizar/criar/excluir. Eu acho que é mais raro atualizar mais de um ou alguns registros por vez, então eu também acho que 'selecionar' é onde os gargalos tendem a ocorrer. Dito isso, você precisa conhecer sua aplicação (perfil). O melhor lugar para focar seu tempo de otimização é quase sempre no nível do banco de dados nas próprias consultas, em vez do código do cliente. O código do cliente é apenas o encanamento:não é a força principal do seu aplicativo. No entanto, como o encanamento tende a ser reutilizado em muitos aplicativos diferentes, simpatizo com o desejo de torná-lo o mais próximo possível do ideal e, portanto, tenho muito a dizer sobre como construir esse código.

Eu tenho um método genérico para selecionar consultas/procedimentos na minha camada de dados que se parece com isso:
private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
{
    //ConnectionString is a private static property in the data layer
    // You can implement it to read from a config file or elsewhere
    using (var cn = new SqlConnection(ConnectionString))
    using (var cmd = new SqlCommand(sql, cn))
    {
        addParameters(cmd.Parameters);

        cn.Open();
        using (var rdr = cmd.ExecuteReader())
        {
            while (rdr.Read())
                yield return rdr;
            rdr.Close();
        }
    }
}

E isso me permite escrever métodos de camada de dados públicos que usam métodos anônimos para adicionar os parâmetros. O código mostrado funciona com .Net 2.0+, mas pode ser escrito ainda mais curto usando .Net 3.5:
public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
{
    //I could easily use a stored procedure name instead of a full sql query
    return Retrieve(
        @"SELECT c.* 
         FROM [ParentTable] p 
         INNER JOIN [ChildTable] c ON c.ParentID = f.ID 
         WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
       {
          p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
       }
     );
}

Vou parar por aqui para poder apontar novamente para o código logo acima que usa o método anônimo para criação de parâmetros.

Este é um código muito limpo, pois coloca a definição de consulta e a criação de parâmetros no mesmo lugar, enquanto ainda permite que você abstraia o código de conexão/chamada do banco de dados clichê para um lugar mais reutilizável. Eu não acho que essa técnica seja coberta por nenhum dos pontos de bala em sua pergunta, e também é muito rápido. Acho que isso cobre o objetivo da sua pergunta.

Eu quero continuar, porém, a explicar como tudo isso se encaixa. O resto é bastante simples, mas também é fácil jogar isso em uma lista ou similar e errar, prejudicando o desempenho. Então, seguindo em frente, a camada de negócios usa uma fábrica para traduzir os resultados da consulta para objetos (c# 3.0 ou posterior):
public class Foo
{
    //various normal properties and methods go here

    public static Foo FooFactory(IDataRecord record)
    {
        return new Foo
        {
            Property1 = record[0],
            Property2 = record[1]
            //...
        };
    }
}

Em vez de tê-los vivos em sua classe, você também pode agrupá-los em uma classe estática especificamente destinada a conter os métodos de fábrica.

Eu preciso fazer uma alteração no método de recuperação original. Esse método "produz" o mesmo objeto repetidamente, e isso nem sempre funciona tão bem. O que queremos fazer diferente para que funcione é forçar uma cópia do objeto representado pelo registro atual, de modo que quando o leitor mudar para o próximo registro estejamos trabalhando com dados limpos. Esperei até depois de mostrar o método de fábrica para que pudéssemos usá-lo no código final. O novo método Retrieve se parece com isso:
private static IEnumerable<T> Retrieve(Func<IDataRecord, T> factory,
                  string sql, Action<SqlParameterCollection> addParameters)
{
    //ConnectionString is a private static property in the data layer
    // You can implement it to read from a config file or elsewhere
    using (var cn = new SqlConnection(ConnectionString))
    using (var cmd = new SqlCommand(sql, cn))
    {
        addParameters(cmd.Parameters);

        cn.Open();
        using (var rdr = cmd.ExecuteReader())
        {
            while (rdr.Read())
                yield return factory(rdr);
            rdr.Close();
        }
    }
}

E agora chamaríamos esse novo método Retrieve() assim:
public IEnumerable<Foo> GetFooChildrenByParentID(int ParentID)
{
    //I could easily use a stored procedure name instead of a full sql query
    return Retrieve(Foo.FooFactory,
        @"SELECT c.* 
         FROM [ParentTable] p 
         INNER JOIN [ChildTable] c ON c.ParentID = f.ID 
         WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
       {
          p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
       }
     );
}

Obviamente, este último método pode ser expandido para incluir qualquer lógica de negócios adicional necessária. Acontece também que esse código é excepcionalmente rápido, porque aproveita os recursos de avaliação lenta do IEnumerable. A desvantagem é que ele tende a criar muitos objetos de curta duração, e isso pode prejudicar o desempenho transacional sobre o qual você perguntou. Para contornar isso, às vezes quebro uma boa camada n e passo os objetos IDataRecord diretamente para a camada de apresentação e evito a criação desnecessária de objetos para registros que são simplesmente vinculados a um controle de grade imediatamente.

O código Atualizar/Criar é semelhante, com a diferença de que você geralmente está alterando apenas um registro por vez, em vez de muitos.

Ou, eu poderia salvar você lendo este longo post e apenas dizer para você usar o Entity Framework;)