O novo Base-Class-Based Provider Model

 

Nas versões 1.0 e 1.1 do ADO.Net os providers implementavam uma série de classes específicas. Por exemplo, System.Data.SqlClient possui uma classe SqlConnection e essa classe implementa IDbConnection. System.Data.OracleClient possui uma classe OracleConnection a qual também implementa IDbConnection. Agora com o novo modelo de providers da versão 2.0 é baseado em uma série de classes base no System.Data.common, e não mais classes específicas como SqlClient e OracleClient. É claro que o suporte a essas classes ainda continua.

 

 

Provider Factories

 

Novamente, nas versões 1.0/1.1 do ADO.Net quando precisávamos de acesso a dados, tínhamos que criar uma classe/objeto específicos referente ao Database usado no cenário: SqlClient, OracleClient ou OleDb. Temos agora, nesse novo modelo, uma nova classe (System.Data.Common.ProviderFactories) que faz isso pra gente, de acordo com um parâmetro informado (ProviderInvariantName). Isso acontece porque cada Provider tem um registro (string) no Machine.Config contendo suas informações. Importando o namespace System.Data.Commom temos acesso a classe DbProviderFactories e ao método GetFactoryClasses, o qual retorna um DataTable contendo todas as informações dos providers. Abaixo vemos como resgatamos isso, e na figura 1 os dados populados em um DataGridView:

 

DataTable dt = DbProviderFactories.GetFactoryClasses();

this.dataGridView1.DataSource = dt;

 

Figura1: Lista de Providers do Machine.Config

 

Observe que na coluna InvariantName temos o nome dos Providers disponíveis, passando isso como parâmetro para o método GetFactory  da classe DbProviderFactories conseguimos então criar um objeto provider específico para o Database informado no parâmetro, neste caso SqlServer. No exemplo abaixo, criamos um provider e instanciamos uma Connection para o SqlServer:

 

IDbConnection conn = null;

string provstring = "System.Data.SqlClient";

DbProviderFactory fact = DbProviderFactories.GetFactory(provstring);

conn = fact.CreateConnection();

 

Observe mais uma vez que não há nada no código especificando explicitamente que a Connection criada deve ser uma SqlConnection. O objeto Factory sabe disso através do InvariantName informado no parâmetro. Então, para mudar essa conexão para Oracle por exemplo, basta alterar o InvariantName para System.Data.OracleClient.

 

 

 

 

Asynchronous Commands

 

Em alguns momentos em nosso código precisamos executar mais de um processo ao mesmo tempo. No ADO.Net 2.0 temos suporte a esse tipo de comando assíncrono, informando um Begin e um End para determinada execução. Abaixo temos os métodos que suportam esse tipo de execução Assíncrona, comparando com os antigos Síncronos:

 

Synchronous Method

Asynchronous Methods

               ExecuteNonQuery

                     BeginExecuteNonQuery, EndExecuteNonQuery

               ExecuteReader

                     BeginExecuteReader, EndExecuteReader

               ExecuteXmlReader

                     BeginExecuteXmlReader, EndExecuteXmlReader

 

Apesar de ser de grande ajuda, não devemos usar esse tipo de comando sem que realmente seja necessário, pois pelo fato de disparar 2 processos paralelos, temos um impacto considerável de performance. O uso é indicado em situações onde já sabemos que um processo poderá demorar muito tempo para ser concluído, ou outros casos do tipo.

Abaixo segue um exemplo interessante com essa nova feature:

 

Em uma aplicação Windows Forms adicionei um novo form chamado Asynchronous, e incluí um TextBox multiline para exibir o texto. dentro deste form seguem os métodos abaixo:

 

private void Asynchronous_Load(object sender, EventArgs e)

{

// Este exemplo mostra o uso do comando BeginExecuteReader

      // WAITFOR simplesmente faz a query aguardar um determinado tempo

      string commandText =

                "WAITFOR DELAY ''00:00:03'';" +

                "SELECT Distinct ShipName FROM Orders " +

                "WHERE ShipName LIKE ''O%'' " +

                "ORDER BY ShipName";

      RunCommandAsynchronously(commandText, GetConnectionString());

      this.txtDisplay.Text = display;

}

 

 

private void RunCommandAsynchronously(string commandText, string connectionString)

{

// Verificamos em uma Thread paralela se o comando foi conluido

using (SqlConnection connection = new SqlConnection(connectionString))

      {

            SqlCommand command = new SqlCommand(commandText, connection);

            connection.Open();

 

            // Pede para o Command executar um comando assincrono[Thread 1]

            IAsyncResult result = command.BeginExecuteReader();

 

            // Verifica se o comando foi conluido[Thread 2]

            int count = 0;

            while (!result.IsCompleted) {

                  count += 1;

                  display += "Waiting ({" + count + "}) " + "\r\n";

                  // Aguardar 10 segundos...

                  System.Threading.Thread.Sleep(100);

            }

            // Ao sair do While significa que a Thread 1 já foi conluída...

            using (SqlDataReader reader = command.EndExecuteReader(result)) {

                  DisplayResults(reader);

            }

        }

}

private void DisplayResults(SqlDataReader reader)

{

      // Exibe os dados do DataReader

      while (reader.Read()){

            // Exibe todas as colunas

            for (int i = 0; i < reader.FieldCount; i++)

                  display += reader.GetValue(i) + "\r\n";

      }

}

 

public static string GetConnectionString()

{

      // Para conexões assincronas é necessário incluir o

      // comando "Asynchronous Processing=true" na String de Conexão

return "Server=SERVER_NAME; User Id=USER_NAME; Password=PASS; Initial      Catalog=Northwind; Asynchronous Processing=true";

}

 

 

Na Figura 2 ao lado podemos ver o resultado da operação, observe que enquanto o processo de execução da query não termina o texto “Waiting” é impresso. Depois do processo terminado entao o retorno da query é impresso.

  

 

Provider Statistics

 

Mais uma das novidades dessa nova plataforma são as Estatísticas. A classe SqlClient agora nos prove a possibilidade de resgatar estatísticas do database através do método RetrieveStatistics da classe SqlConnection.

Este recurso vem desabilitado por default, e devemos habilita-lo para fazer uso.

Abaixo temos um exemplo do uso das estatísticas:

 

string connectionString = GetConnectionString();

using (SqlConnection awConnection = new SqlConnection(connectionString))

{

// StatisticsEnabled é Falso por default

      // Devemos setar como True para o processo ser iniciado

      awConnection.StatisticsEnabled = true;

 

      // Resgatando os dados

      string productSQL = "SELECT * FROM Products";

      SqlDataAdapter productAdapter = new SqlDataAdapter(productSQL, awConnection);

      DataSet awDataSet = new DataSet();

      awConnection.Open();

      productAdapter.Fill(awDataSet, "ProductTable");

 

// Depois de executado o comando, resgatamos as informações das estatísticas

      IDictionary currentStat = awConnection.RetrieveStatistics();

 

      // Imprimindo os valores individuais,

      // relacionados ao comando que acabou de ser executado

      long bytesReceived = (long)currentStat["BytesReceived"];

      long bytesSent = (long)currentStat["BytesSent"];

      long selectCount = (long)currentStat["SelectCount"];

      long selectRows = (long)currentStat["SelectRows"];

 

      this.txtEst.Text = "Total Counters: " + currentStat.Count.ToString() + "\r\n";

this.txtEst.Text += "BytesReceived: " + bytesReceived.ToString() + "\r\n";

      this.txtEst.Text += "BytesSent: " + bytesSent.ToString() + "\r\n";

      this.txtEst.Text += "SelectCount: " + selectCount.ToString() + "\r\n";

      this.txtEst.Text += "SelectRows: " + selectRows.ToString() + "\r\n";

}

No exemplo acima, foi usado um windows form contendo um TextBox multiline, e o código apresentado foi colocado no evento Load.

 

 

AttachDbFileName

 

Agora, a classe SqlClient possui uma nova opção chamada AttachDbFileName, com essa nova feature temos a possibilidade de dizer diretamente na connection string o nome do arquivo do database que deve ser atachado na instancia do usuário (User Instance). Uma User Instance  é similar a uma instancia normal do Sql Server, as ela é criada em tempo de execução, de acordo com parâmetros passados na connection string.

 

AttachDbFilename=|DataDirectory|\Database1.mdf;

 

A opção DataDirectory determina o diretorio onde está o arquivo do database(.mdf) e é onde deve estar também o arquivo de log(.ldf). Caso já exista um database com este mesmo nome, a conexão abrirá o já existente.

Quando fazemos uso deste recurso, o usuário com direitos sobre essa instancia criada será o mesmo usuário do Windows o qual abriu a conexão. Por exemplo, se o usuário Paulo abriu uma conexão especificando a opção UserInstance na connection string, entao essa User Instance terá Paulo como user account.

Como disse anteriormente, uma User Instance é criada quando passamos esse parâmetro na connection string. Abaixo temos um exemplo deste caso: