INTRODUÇÃO AO MODELO ADO.NET (DESCONECTADO) O modelo ADO.NET (Activex Data Objects .NET) consiste num conjunto de classes definidas pela .NET framework (localizadas no namespace System.Data) que pode ser utilizado para aceder aos dados armazenados numa base de dados remota. A seguir apresentam-se os principais objectos do “modelo desconectado” ADO.NET. Dataset Data Table .NET Data Provider Data Adapter Command Servidor de BD Connection BD O “modelo desconectado” ADO.NET utiliza dois tipos de objectos para aceder à base de dados: os objectos Dataset, que podem conter um ou mais objectos Data Table, e os objectos .NET Data Provider. Um Data Provider representa um conjunto de componentes (DataAdapter, DataReader, Command e Connection) que permite estabelecer a ligação a uma fonte de dados, executar comandos (SQL) e devolver o seu resultado. O objecto Connection possibilita a ligação a uma fonte de dados. O objecto Command permite aceder e manipular a base de dados (representa um comando SQL). O objecto Data Reader permite acesso só de leitura e sequencial à base de dados. O objecto Data Adapter serve de ponte entre a base de dados e o objecto Dataset. Para tal, o objecto Data Adapter utiliza objectos Command para executar instruções SQL na base de dados. O objecto Dataset contém um ou mais objectos Data Table. Assim, os dados que vão “alimentar” a aplicação estão armazenados neste objecto. O modelo ADO.NET permite trabalhar com cenários conectados e desconectados, embora tenha sido construído a pensar, essencialmente, numa arquitectura desconectada. De modo simplista, no cenário conectado existe permanentemente uma ligação activa à base de dados enquanto os dados são manipulados. O anterior modelo ADO foi pensado para uma arquitectura deste género, apesar de também permitir recorrer à técnica conhecida como disconnected recordset. Basicamente, no modelo desconectado, o objecto Data Adapter utiliza um objecto Command para executar uma instrução SQL (tipicamente um comando Select) para obter os dados. O objecto Command utiliza um objecto Connection para estabelecer uma ligação com a base de dados e obter os dados pretendidos. Depois, o objecto Data Adapter armazena os dados num objecto Data Table do Objecto Dataset. Note-se que, no modelo desconectado, a ligação à base de dados apenas permanece aberta durante o tempo necessário para obter ou alterar os dados da base de dados. Depois, essa ligação é fechada e a aplicação trabalha com “a cópia” dos dados através do objecto Dataset. Portanto, os dados armazenados no Dataset são independentes dos dados registados na base de dados. Todavia, as alterações realizadas sobre os dados existentes no Dataset são posteriormente reflectidas na base de dados. Para actualizar a base de dados com as alterações efectuadas sobre o Dataset, o objecto Data Adapter utiliza um objecto Command que define um comando Insert, Update ou Delete. Depois, o objecto Command usa um objecto Connection para estabelecer uma ligação com a base de dados e executar a instrução de actualização. Posteriormente, a ligação é fechada. ADO.NET DATA PROVIDERS Todos os objectos do modelo ADO.NET são implementados por classes definidas no espaço de nomes (namespace) System.Data da .NET framework. Contudo, as classes específicas utilizadas para implementar os objectos Data Adapter, Command e Connection dependem do Data Provider que é utilizado. Actualmente, a .NET framework inclui quatro Data Providers: o SQL Server Data Provider, que foi desenhado para providenciar acesso a servidores SQL Server, o OLE DB Data Provider, que permite aceder a qualquer motor de base de dados que suporte o interface OLE DB, o ODBC .NET Data Provider, que permite aceder a fontes de dados através de ODBC, e o .NET Data Provider for Oracle, que permite o acesso a bases de dados Oracle. Adicionalmente a estes quatro Data Providers, existem outros que foram desenvolvidos pelos fabricantes, para permitirem um acesso optimizado às suas bases de dados. Por exemplo, existem Data Providers específicos para servidores MySQL, SQL Anywhere, etc. Para se poder utilizar um .NET Data Provider, deve-se adicionar, no início do ficheiro, uma instrução de Import para o espaço de nomes apropriado. Caso contrário, tem que se qualificar cada classe que se refira a estes espaços de nomes, dado que estes espaços de nomes não são incluídos, por defeito, como referências. Por exemplo, para utilizar o OLE DB Data Provider pode-se utilizar o seguinte código: Imports System.Data.OleDb . . Dim cnAlunos As New OleDbConnection() Ao não utilizar a instrução de Import teria que usar o seguinte código: Dim cnAlunos As New OleDb.OleDbConnection() As principais classes incluídas no OLE DB Data Provider são: OleDbConnection, OleDbCommand, OleDbDataReader e OleDbDataAdapter. A seguir, apresentam-se, resumidamente, as suas principais propriedades e métodos. Classe OleDbConnection Propriedades Descrição ConnectionString Contém informação que permite estabelecer a ligação a uma base de dados (nome do servidor, nome da base de dados, informação de login). Métodos Descrição Open Abre uma ligação com uma base de dados. Close Fecha uma ligação com uma base de dados. Notas: A utilização de um objecto connection passa pela definição de uma string de ligação; pela criação do objecto connection; pela abertura da ligação e pelo fecho da ligação. Existem, essencialmente, duas formas de criar um objecto connection a partir do VS.NET: em design time e em run time. Em design time, ou seja, antes da execução do código, existem alguns wizards que nos auxiliam na sua criação (DataAdapter Configuration Wizard, Server Explorer, etc.). Em run time, podemos criar um objecto connection do seguinte modo: Imports System.Data.OleDbClient . .Dim con As New OleDbConnection() Dim constring As String constring = "Provider=""MSDAORA.1""; User ID=abd1; Data Source=arturdb; Password=abd1" Con.ConnectionString = constring Con.Open . Con.Close Após a utilização de um objecto connection é extremamente importante que o mesmo seja fechado, pois libertará recursos não só do lado do cliente, como também, e sobretudo, do lado do servidor, melhorando assim o desempenho da aplicação. Caso o objecto connection não seja fechado de forma explícita, o garbage collector do VS.NET vai depois tratar da libertação dos respectivos recursos, assim que uma variável saia do seu âmbito (scope). Todavia, não será prejudicial ao desempenho de uma aplicação a abertura e fecho sistemático de uma ligação à base de dados? Se o objectivo for a criação de uma aplicação para meia dúzia de utilizadores em simultâneo, então certamente que uma boa prática será a criação da ligação à base de dados assim que a aplicação inicia e o seu fecho assim que a aplicação termina. Por outro lado, se a aplicação for construída para funcionar em ambiente distribuído para centenas ou milhares de utilizadores, então a ligação à base de dados deve ser libertada o mais cedo possível. Note-se que, na prática, ao invocar o método close(), estaremos apenas fazer uma desactivação da ligação à base de dados, para minimizar os recursos utilizados, à espera que seja invocada uma nova ligação com as mesmas características, para reactivar essa ligação (em vez de criar uma nova). A este processo dá-se, vulgarmente, o nome de connection pooling (o aspecto chave a reter será manter a mesma connection string). Classe OleDbCommand Propriedades Connection Descrição O objecto OleDbConnection que é utilizado, pelo objecto command, para estabelecer a ligação à base de dados. CommandText Comando SQL, nome de um procedimento armazenado ou o nome de uma tabela. CommandType Uma constante que indica se a propriedade CommandText contém um comando SQL (Text), o nome de um procedimento armazenado (StoredProcedure), ou o nome de uma tabela (TableDirect). Parameters A colecção de parâmetros do objecto Command. Métodos Descrição ExecuteReader Executa uma consulta e devolve o resultado na forma de um objecto OleDbDataReader. Por outras palavras, retorna uma referência a um objecto OleDbDataReader que conterá um conjunto de registos devolvidos da base de dados. Para tal, deverse-á atribuir a execução deste método a uma variável do tipo OleDbDataReader. ExecuteScalar Executa a consulta e devolve, por exemplo, o valor da (primeira) coluna do (primeiro) registo. ExecuteNonQuery Executa o comando e devolve um inteiro que indica o número de registos afectados (aconselhado para utilizar com comandos Insert, Update e Delete). Notas: Quando se utiliza um objecto Data Adapter, a ligação à base de dados é aberta e fechada de forma automática. Porém, se a ligação já se encontrar aberta, terá de ser fechada manualmente. É também possível utilizar os métodos do objecto Data Adapter para executar as instruções SQL do objecto Command. Tal como no caso do objecto connection, também um objecto command pode ser criado em design time, através de um DataAdapter Configuration Wizard ou do Server Explorer, e em run time, do seguinte modo: . . Dim con As New OleDbConnection() Dim constring As String Dim cmd As New OleDbCommand() Dim rdr As New OleDbDataReader . . cmd.Connection = con cmd.CommandText = “Select nome, morada From Clientes” cmd.CommandType = CommandType.Text rdr = cmd.ExecuteReader (CommandBehavior.CloseConnection) . . Resumindo, o primeiro passo a realizar após a criação de um objecto command será associar-lhe um objecto connection. Depois, deve-se indicar o comando (SQL) a executar e o seu tipo. Note-se que, no método Executereader, podemos indicar, através de um parâmetro (com a instrução CommandBehavior.CloseConnection), que se pretende fechar automaticamente a ligação assim que é devolvida a referência ao objecto OleDbDataReader. Classe OleDbDataAdapter Propriedades SelectCommand DeleteCommand InsertCommand UpdateCommand Métodos Fill Update Descrição Objecto OleDbCommand que inclui uma instrução Select para consultar a base da dados. Objecto OleDbCommand que inclui uma instrução Delete para eliminar registos da base da dados. Objecto OleDbCommand que inclui uma instrução Insert para adicionar registos à base da dados Objecto OleDbCommand que inclui uma instrução Update para alterar registos da base de dados Descrição Executa o objecto Command identificado pela propriedade SelectCommand e regista o resultado num objecto Dataset. Executa os objectos Command identificados pelas propriedades DeleteCommand, InsertCommand e UpdateCommand, por cada registo do Dataset que tenha sido eliminado, inserido ou alterado. (ver método HasChanges do objecto DataSet. Este método vai verificar a propriedade RowState (Added, Deleted, Modified, Unchanged) de cada registo do DataSet para verificar se foram efectuadas alterações ao DataSet) Notas: Quando é usado o método Fill para obter dados da base de dados, o objecto Data Adapter utiliza um objecto Data Reader para ler os registos do resultado devolvido pela consulta e armazenar esses registos num objecto Dataset. Depois, a aplicação pode trabalhar com os dados do Dataset sem afectar os dados da base de dados. Porém, se a aplicação efectuar alterações aos dados do Dataset, pode ser utilizado o método Update do objecto Data Adapter para executar os objectos Command identificados pelas propriedades DeleteCommand, InsertCommand e UpdateCommand para registar essas alterações na base de dados. Quando é executado o método Update é aberta automaticamente uma ligação. Essa ligação é também fechada automaticamente quando a actualização for concluída. Um objecto Data Adapter pode ser criado em design time, através de um DataAdapter Configuration Wizard ou do Server Explorer, e em run time, do seguinte modo: . . Dim dad As New OleDbDataAdapter() Dim con As New OleDbConnection() Dim cmd As New OleDbCommand() Dim ds As New OleDbDataset() Dim constring As String . . cmd.Connection = con cmd.CommandText = “Select nome, morada From Clientes” cmd.CommandType = CommandType.Text dad.SelectCommand = cmd dad.fill(ds, “Clientes”) ‘2.º arg (não obrig.)Æ nome da data table do dataset ds . . No código anterior os objectos Command e Connection foram criados explicitamente e só depois foram associados ao objecto DataAdapter. Porém, existe ainda a possibilidade de indicar implicitamente qual a instrução Select e qual a ligação à base de dados a utilizar pelo objecto DataAdapter: . . From Clientes”, Dim dad As New OleDbDataAdapter(“Select nome, morada "Provider=""MSDAORA.1""; User ID=abd1; Data Source=arturdb; Password=abd1") A grande vantagem de utilizar este método reside no facto de a criação e a destruição dos objectos Command e Connection ser gerida pelo próprio Data Adapter. O primeiro método é sobretudo aconselhável em situações de reutilização dos objectos em questão. Classe OleDbDataReader Propriedades Item FieldCount Métodos Read Close Descrição Acede à coluna com o índice ou nome especificado do registo actual. Número de colunas do registo actual. Descrição Lê o próximo registo. Devolve True se existirem mais registos. Caso contrário, devolve False. Fecha o objecto Data Reader. Note-se que o objecto Data Reader permite ler dados da base de dados, mas não permite modificá-los. Por outras palavras o Data Reader é um objecto só de leitura. Por outro lado, ele só permite uma leitura sequencial, isto é, mal se aceda a um registo, o registo anterior passa a estar inacessível. O objecto Data Reader só pode ser criado a partir de um objecto Command: Dim cmd As New OleDbCommand() Dim rdr As New OleDbDataReader rdr = cmd.ExecuteReader While (rdr.read()) . . End While rdr.close Note-se ainda que os nomes das classes do .NET OLE DB Data Provider têm o prefixo OleDB. Classe Dataset Um objecto Dataset consiste num repositório de dados em memória, no lado do cliente, e apresenta-se como o principal representante do conjunto de objectos do modelo ADO.NET a suportar a arquitectura desconectada. Um objecto Dataset nada sabe acerca da fonte de dados de onde provêm os seus dados, podendo estes provirem de diferentes bases de dados. Isto acontece porque, o objecto Data Adapter serve de ponte entre o DataSet e a base de dados, pois utiliza a informação de como ligar e aceder à base de dados (a partir dos objectos connection e command), ao mesmo tempo que proporciona métodos para preencher o DataSet de dados, assim como para efectivar a alteração dos dados, realizada ao nível do DataSet, na fonte de dados. Um objecto Dataset consiste numa hierarquia de um ou mais objectos Data Table e Data Relation. Um objecto Data Table consiste de um ou mais objectos Data Column, Data Row e Constraint. Um objecto Data Relation define o modo como as tabelas de um objecto Dataset estão relacionadas. O objecto Data Relation é essencialmente utilizado para gerir as restrições e, desse modo, simplificar a navegação entre tabelas relacionadas. Os objectos Data Column definem os dados em cada coluna da tabela, incluindo o seu nome, tipo de dados, etc. Os objectos Data Row contêm os dados para cada registo da tabela. Os objectos Constraint são utilizados para manter a integridade dos dados. Uma restrição de chave primária assegura que os valores numa coluna, tal como a coluna de chave primária, são únicos. Uma restrição de chave forasteira determina como os registos de uma tabela são afectados quando os registos correspondentes duma tabela relacionada são alterados ou eliminados. Note-se que todos os objectos de um Dataset são armazenados em colecções. Por exemplo, os objectos Data Table são armazenados numa colecção de objectos Data Table, e os objectos Data Row são armazenados numa colecção de objectos Data Row. Pode-se aceder a estas colecções através das propriedades dos respectivos objectos.