C# - Usando código independente do banco de dados


Como fazer se você precisar escrever o seu código de acesso a dados de forma que ele possa ser configurado para trabalhar com qualquer banco de dados relacional suportado por um provedor ADO .NET ?

A primeira coisa a fazer é saber se realmente você precisa desse recurso caso contrário você vai trabalhar em vão. Portanto pense bem antes de iniciar a tarefa visto que não é todo o dia que uma empresa decide trocar o seu banco de dados. Em geral a troca de um banco de dados afeta a empresa como um todo e por isso somente em casos muito extremos elas tomam a decisão de fazer a troca do fornecedor do banco de dados.

Mas vamos supor que você quer por que quer ter o seu código de maneira que ele funcione com os principais RDBMS suportados pelos provedores ADO .NET. Estou falando do SQL Server, MySQL, Oracle, MSAccess, etc...

Se você não tem a mínima ideia de como fazer esta tarefa este artigo vai lhe mostrar o caminho feliz...

O caminho feliz é programar usando as interfaces dos provedores de dados ADO .NET ao invés de usar suas classes concretas e dessa forma não depender dos recursos e dos tipos de dados que são específicos para cada implementação. Utilize também as fábricas de classes e métodos para instanciar os objetos dos provedores de dados que você pretende utilizar.

Entendeu ???

Uma das regras de ouro da orientação a objetos é: "Programe para uma interface e não para uma implementação."

E se você não entendeu até hoje o que isso significa quem sabe hoje usando o enfoque deste artigo tudo isso fique mais claro para você.

Quando você usa uma implementação específica para um provedor de dados , como por exemplo o provedor de dados SQL Server, isso pode simplificar o seu código e pode ser adequado se você precisar dar suporte a apenas a um único tipo de banco de dados ou acessar os recursos específicos fornecidos por este provedor.

Este caminho no entanto fará com que você tenha que reescrever e testar o seu código novamente se você quiser usar um provedor de dados diferente em consequência de uma troca do banco de dados que estava usando inicialmente. Você terá que reescrever toda a sua camada de acesso aos dados, isso se você adotou uma arquitetura em camada separando as responsabilidades. Se você não fez isso o trabalho será muito maior.

E quais são essas interfaces que você precisa conhecer ?

Abaixo temos resumo das principais interfaces que você deve programar quando for escrever o seu código genérico para trabalhar com qualquer provedor de dados ADO .NET:

Interface Descrição
IDbConnection Representa uma conexão com um banco de dados relacional. Você deve programar a lógica para criar um objeto de conexão do
tipo apropriado com base nas informações de configuração de seu aplicativo , ou usar o método factory DbProviderFactory.CreateConnection
IDbCommand Representa um comando SQL que é emitido para um banco de dadaos relacional . Você pode criar objetos IDbCommand do tipo apropriado
usando o IDbConnection.CreateCommand ou o método factory DbProviderFactory.CreateCommand
IDataParameter Representa um parâmetro para um objeto IDbCommand. Você pode criar objetos IDataParameter do tipo correto usando o IDbCommand.Parameters.Add IDbCommand.CreateParameter, ou o método factory DbProviderFactory.CreateParameter.
IDataReader Representa o conjunto de resultados de uma consulta de banco de dados e fornece acesso para as linhas e colunas. Um objeto do tipo correto será devolvido quando você chamar o o método IDbCommand.ExecuteReader.
IDbDataAdapter Representa o conjunto de comandos utilizados para preencher uma System.Data.DataSet a partir de um banco de dados relacional e atualizar o
banco de dados com base nas alterações do DataSet. Você deve programar a lógica para criar um objeto adaptador de dados do tipo apropriado
com base em informações de configuração de seu aplicativo ou usar o método DbProviderFactory.CreateAdapter

A classe System.Data.Common.DbProviderFactory fornece um conjunto de métodos de fábrica para a criação de todos os tipos de objetos de provedor de dados, tornando-a muito útil para a implementação de código de banco de dados genéricos.

A mais importante delas é a classe DbProviderFactory que fornece um mecanismo para a obtenção de uma instância IDbConnection inicial, que é o ponto de partida crítico para escrever código ADO.NET genéricos.

Cada implementação de provedor de dados padrão (exceto o SQL Server CE) inclui uma única classe factory derivada de DbProviderFactory.

A seguir temos uma relação das subclasses de DbProviderFactory:

Você pode obter uma instância da subclasse DbProviderFactory apropriada usando a classe DbProviderFactories , que é, efetivamente, uma fábrica de fábricas. Cada fábrica de provedor de dados é descrita por informações de configuração no arquivo machine.config.

Abaixo temos um exemplo para o data adatpter SQL Server. (Isto pode ser alterado ou substituído por informações de configurações específicas de aplicações se necessário.)

<configuration>
<system.data>
<DbProviderFactories>
   
<add name="SqlClient Data Provider" invariant="System.Data.SqlClient" description=".Net Framework Data Provider for SqlServer"    type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
   
<add name="Odbc Data Provider" ... />
   <add name="OleDb Data Provider" ... />
   <add name="OracleClient Data Provider" ... />
   <add name="SQL Server CE Data ... />

</DbProviderFactories>
</system.data>
</configuration>

Você pode enumerar as fábricas de provedores de dados disponíveis chamando DbProviderFactories.GetFactoryClasses, que retorna uma System.Data.DataTable contendo as seguintes colunas:

Normalmente, você permitiria que o provedor fosse selecionado no momento da instalação ou na primeira vez que a aplicação fosse executada, e, em seguida, armazenaria as configurações como usuário ou como dados de configuração de aplicativo. A peça mais importante da informação é o InvariantName, que você passa para o método DbProviderFactories.GetFactory para obter a implementação DbProviderFactory que você vai usar para criar suas instâncias de IDbConnection.

Vejamos a seguir um exemplo prático que procura ilustrar como usar toda essa teoria...

Exemplo Prático

Neste exemplo vamos demonstrar a enumeração de todos os provedores de dados configurados para a máquina local e máquina e aplicação.

Ele utiliza a classe DbProviderFactories para instanciar um objeto DbProviderFactory objeto (na verdade um SqlClientFactory) a partir do qual cria o IDbConnection apropriado.

Em seguida, usamos os métodos de fábrica das interfaces de provedor de dados para criar outros objetos necessários, resultando em um código genérico.

Abra o Visual C# 2010 Express Edition e crie um novo projeto do tipo Windows Forms Application com o nome DbProviderFactory_Exemplo;

A seguir no formulário form1.cs inclua dois controles Buttons e dois controles ListBox conforme o leiaute abaixo:

Vamos iniciar declarando os namespaces usados:

using System;
using System.Data;
using System.Windows.Forms;
using System.Data.Common;

A sguir vamos definir o código do evento Click do botão de comando para obter a lista de provedores instalados conforme abaixo:

   private void btnProvedores_Click(object sender, System.EventArgs e)
        {
          
 // Obtêm a lista de provedores de dados ADO.NET registros na máquina e no arquivo de configuração
            using (DataTable providers = DbProviderFactories.GetFactoryClasses())
            {
            
   // Enumera o conjunto de provedoers de dados e exibe os detalhes
                lstProvedores.Items.Add("ADO.NET Data Providers Disponíveis :");
                lstProvedores.Items.Add(" ");
                foreach (DataRow prov in providers.Rows)
                {
                    lstProvedores.Items.Add(" Nome:          " + prov["Name"]);
                    lstProvedores.Items.Add(" Descrição:     " + prov["Description"]);
                    lstProvedores.Items.Add(" Nome Invariant " + prov["InvariantName"]);
                    lstProvedores.Items.Add("");
                }
            }
        }

Executando o projeto e clicando neste botão iremos obter:

Obs: Estes são os provedores instalados na minha máquina a sua lista pode ser diferente.

Agora vamos definir o código do evento Click do botão de comando que mostra o exemplo de acesso ao SQL Server:

    private void btnAcessoDados_Click(object sender, System.EventArgs e)
        {
            // Obtêm o DbProviderFactory para o SQL Server, o provedor a usar
            // poderia ser selecionado pelo usuário ou lido a partir de um arquivo de configuração
            // Neste caso nos simplesmente pssariamos o nome invariant

            DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");

            // Usa o DbProviderFactory para criar o IDbConnection inicial e
            // então os métodos factory dos provedores de dados para os demais objetos

            using (IDbConnection con = factory.CreateConnection())
            {
                // Normalmente le a string de conexão de um local seguro
                // Neste exemplo usamos um valor padrão

                con.ConnectionString = @"Data Source = .\sqlexpress;" + "Database = Northwind; Integrated Security=SSPI";

                // Cria e configura um novo comando
                using (IDbCommand com = con.CreateCommand())
                {
                    com.CommandType = CommandType.StoredProcedure;
                    com.CommandText = "Ten Most Expensive Products";
                    // Abre a conexão
                    con.Open();
                    // Executa o comando e processa o resultado
                    using (IDataReader reader = com.ExecuteReader())
                    {
                        lstDados.Items.Add(Environment.NewLine);
                        lstDados.Items.Add("Preço dos 10 Produtos mais caros.");
                        lstDados.Items.Add(Environment.NewLine);
                        while (reader.Read())
                        {
                            // Exibe os detalhes dos produtos
                            lstDados.Items.Add(" " + reader["TenMostExpensiveProducts"] + "\t\t"  + reader["UnitPrice"]);
                        }
                    }
                }
            }

No exemplo estamos acessando a consulta armazenada Ten Most Expensive Products do banco de dados Northwind.mdf.

O resultado obtido para este código é visto a seguir:

O exemplo usado foi especifico para o SQL Server mas em um código genérico você poderia fazer assim:

1- Definir os provedores que deseja usar e que estejam disponíveis no arquivo de configuração da aplicação:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="sqlProvider" value="System.Data.SqlClient" />
    <add key="oledbProvider" value="System.Data.Oledb" />
    <add key="MySqlProvider" value="System.Data.MySqlClient" />
    <add key="OdbcProvider" value="System.Data.Odbc" />
  </appSettings>
</configuration>

2- Ler o provedor de dados específico e obter o seu nome invariante, criar o DbProviderFactory, criar a string de conexão e montar a string de conexão conforme o provedor.

    string sInvariant = ConfigurationManager.AppSettings["oledbProvider"];
    DbProviderFactory df = DbProviderFactories.GetFactory(sInvariant);
            
     DbConnectionStringBuilder csb = df.CreateConnectionStringBuilder();
    string paramName = string.Empty;

    switch (sInvariant) {
           case "System.Data.SqlClient":
	            csb.Add("Data Source", "localhost");
	            csb.Add("Initial Catalog", "Northwind");
	            csb.Add("Integrated Security", "SSPI");
	            paramName = "@nomeparametro";
	            break;
            case "System.Data.OleDb":
	            csb.Add("Provider", "SQLOLEDB");
	            csb.Add("Data Source", "localhost");
	            csb.Add("Initial Catalog", "Northwind");
	            csb.Add("Integrated Security", "SSPI");
	            paramName = "?";
	            break;
            case "System.Data.Odbc":
	            csb.Add("Driver", "{SQL Server}");
	            csb.Add("Server", "localhost");
	            csb.Add("Database", "Northwind");
	            csb.Add("Trusted_Connection", "yes");
	            paramName = "?";
	            break;
            default:
	            return;
            break;
   }

Este é apenas uma indicação de como fazer mas existem outras maneiras de se obter o mesmo resultado.

Aguarde que em outro artigo irei criar um exemplo completo sobre este assunto usando a linguagem C#.

Pegue o projeto completo aqui :  DbProviderFactory_Exemplo.zip

1Timóteo 2:5 Porque há um só Deus, e um só Mediador entre Deus e os homens, Cristo Jesus, homem,
1Timóteo 2:6
o qual se deu a si mesmo em resgate por todos, para servir de testemunho a seu tempo;

1Timóteo 2:7
para o que (digo a verdade, não minto) eu fui constituído pregador e apóstolo, mestre dos gentios na fé e na verdade.

Referências:


José Carlos Macoratti