Uma das boas práticas que sempre foram pregadas ao escrever código de acesso à dados é abrir a conexão o mais tarde e fechar o mais cedo possível. Isso deve-se ao fato de que acessar algum recurso deste tipo é uma tarefa custosa e, caso não seja dada a devida atenção, podemos ter alguns problemas relacionados a performance.
Há alguns cenários onde essa técnica sofre uma pequena variação. Um exemplo é quando há várias queries a serem executadas e, se a cada uma delas abrirmos e fecharmos a conexão, isso irá piorar consideravelmente. Neste caso, é nítida a necessidade da reutilização de uma mesma conexão para efetuar os comandos. Ainda abriremos o mais tarde e fecharemos o mais cedo possível, mas aumentando o intervalo de tempo entre essas duas ações.
Quando trabalhamos com o LINQ To SQL, temos uma classe derivada de DataContext que encapsula o acesso os dados. A cada execução, essa classe abrirá a conexão, executará o comando desejado e, em seguida, fechará a conexão com o respectivo banco de dados. Utilizando o DataContext envolvido ou não em um bloco using, ele sempre executará estes passos para cada uma das queries executadas a partir dele, ou seja, é o comportamento padrão.
Assim como já era fornecido pela classe SqlDataAdapter, há uma versão do construtor da classe DataContext que possibilita fornecer uma conexão já aberta para que ele utilize-a por todas as queries. Basicamente o que ele faz é identificar se a instancia da conexão que é passada como parametro para a DataContext já está aberta; caso esteja, depois de executar a query, ela não será fechará, nos obrigando a fechá-la explicitamente. Se analisarmos os internals da classe DataContext, veremos que existe uma classe não documentada chamada SqlConnectionManager que é responsável por essa manipulação.
Em alguns testes, podemos notar a diferença entre os dois modos de acesso, ou seja, aquele convencional, onde cada comando abre e fecha a conexão, e o segundo modo que reutiliza a mesma conexão para todos os comandos. No primeiro caso, temos os seguintes resultados:
[ Modo Convencional ]
using (DataClasses1DataContext ctx =
new DataClasses1DataContext(“CONNECTION_STRING”))
{
//executar várias queries
}
01: 00:00:01.3890421
02: 00:00:00.9600488
03: 00:00:00.9668471
04: 00:00:01.0287346
05: 00:00:00.9587953
06: 00:00:01.1188069
07: 00:00:01.0369689
08: 00:00:01.0047073
09: 00:00:00.9850514
10: 00:00:00.9772066
Utilizando a segunda opção, devemos criar a conexão com o banco de dados e abrí-la explicitamente antes de passá-la para o construtor da classe DataContext. É importante lembrar que neste caso, a conexão não será fechada implicitamente. Dependendo da frequencia de utilização desta técnica, a criação deste código poderá ser repetitiva. Para encapsular todo esse trabalho, criei uma classe chamada ConnectedDataContext<TDataContext>. Abaixo consta a sua definição:
public class ConnectedDataContext<TDataContext> : IDisposable where TDataContext : DataContext
{
//implementação
}
[ Reutilizando uma mesma conexão ]
using (ConnectedDataContext<DataClasses1DataContext> ctx =
new ConnectedDataContext<DataClasses1DataContext>(“CONNECTION_STRING”))
{
//executar várias queries
}
01: 00:00:01.0577727
02: 00:00:00.7618577
03: 00:00:00.6605342
04: 00:00:00.6552123
05: 00:00:00.6637952
06: 00:00:00.6604886
07: 00:00:00.6363001
08: 00:00:00.6754408
09: 00:00:00.7525822
10: 00:00:00.6538641