Encapsulamento de modificações de banco de dados em uma transação (VB)

por Scott Mitchell

Baixar PDF

Este tutorial é o primeiro de quatro que analisa a atualização, a exclusão e a inserção de lotes de dados. Neste tutorial, aprendemos como as transações de banco de dados permitem que as modificações em lote sejam realizadas como uma operação atômica, o que garante que todas as etapas sejam bem-sucedidas ou todas as etapas falhem.

Introdução

Como vimos começando com o tutorial Uma Visão geral de inserção, atualização e exclusão de dados , o GridView fornece suporte interno para edição e exclusão em nível de linha. Com alguns cliques do mouse, é possível criar uma interface de modificação de dados avançada sem escrever uma linha de código, desde que você tenha conteúdo com edição e exclusão por linha. No entanto, em determinados cenários isso é insuficiente e precisamos fornecer aos usuários a capacidade de editar ou excluir um lote de registros.

Por exemplo, a maioria dos clientes de email baseados na Web usa uma grade para listar cada mensagem em que cada linha inclui uma caixa de seleção junto com as informações de email (assunto, remetente e assim por diante). Essa interface permite que o usuário exclua várias mensagens verificando-as e clicando em um botão Excluir Mensagens Selecionadas. Uma interface de edição em lote é ideal em situações em que os usuários geralmente editam muitos registros diferentes. Em vez de forçar o usuário a clicar em Editar, fazer a alteração e clicar em Atualizar para cada registro que precisa ser modificado, uma interface de edição em lote renderiza cada linha com sua interface de edição. O usuário pode modificar rapidamente o conjunto de linhas que precisam ser alteradas e salvar essas alterações clicando em um botão Atualizar Tudo. Neste conjunto de tutoriais, examinaremos como criar interfaces para inserir, editar e excluir lotes de dados.

Ao executar operações em lote, é importante determinar se deve ser possível que algumas das operações no lote tenham êxito enquanto outras falham. Considere uma interface de exclusão em lote – o que deve acontecer se o primeiro registro selecionado for excluído com êxito, mas o segundo falhará, digamos, devido a uma violação de restrição de chave estrangeira? A exclusão do primeiro registro deve ser revertida ou é aceitável que o primeiro registro permaneça excluído?

Se você quiser que a operação em lotes seja tratada como uma operação atômica, em que todas as etapas tenham êxito ou todas as etapas falhem, a Camada de Acesso a Dados precisará ser aumentada para incluir suporte para transações de banco de dados. As transações de banco de dados garantem atomicidade para o conjunto de INSERTinstruções , UPDATEe DELETE executadas sob o guarda-chuva da transação e são um recurso compatível com a maioria de todos os sistemas de banco de dados modernos.

Neste tutorial, veremos como estender o DAL para usar transações de banco de dados. Os tutoriais subsequentes examinarão a implementação de páginas da Web para inserir, atualizar e excluir interfaces em lote. Vamos começar!

Observação

Ao modificar dados em uma transação em lote, a atomicidade nem sempre é necessária. Em alguns cenários, pode ser aceitável ter algumas modificações de dados bem-sucedidas e outras no mesmo lote falharem, como ao excluir um conjunto de emails de um cliente de email baseado na Web. Se houver um erro de banco de dados no meio do processo de exclusão, provavelmente é aceitável que esses registros processados sem erro permaneçam excluídos. Nesses casos, o DAL não precisa ser modificado para dar suporte a transações de banco de dados. Há outros cenários de operação em lotes, no entanto, em que a atomicidade é vital. Quando um cliente move seus fundos de uma conta bancária para outra, duas operações devem ser executadas: os fundos devem ser deduzidos da primeira conta e, em seguida, adicionados ao segundo. Embora o banco possa não se importar em ter o primeiro passo bem-sucedido, mas o segundo passo falhar, seus clientes ficariam compreensivelmente chateados. Recomendo que você trabalhe neste tutorial e implemente os aprimoramentos no DAL para dar suporte a transações de banco de dados, mesmo que você não planeje usá-las no lote inserindo, atualizando e excluindo interfaces que criaremos nos três tutoriais a seguir.

Uma visão geral das transações

A maioria dos bancos de dados inclui suporte para transações, que permitem que vários comandos de banco de dados sejam agrupados em uma única unidade lógica de trabalho. Os comandos de banco de dados que compõem uma transação têm a garantia de serem atômicos, o que significa que todos os comandos falharão ou todos terão êxito.

Em geral, as transações são implementadas por meio de instruções SQL usando o seguinte padrão:

  1. Indique o início de uma transação.
  2. Execute as instruções SQL que compõem a transação.
  3. Se houver um erro em uma das instruções da Etapa 2, reverta a transação.
  4. Se todas as instruções da Etapa 2 forem concluídas sem erro, confirme a transação.

As instruções SQL usadas para criar, confirmar e reverter a transação podem ser inseridas manualmente ao escrever scripts SQL ou criar procedimentos armazenados ou por meios programáticos usando ADO.NET ou as classes no System.Transactions namespace. Neste tutorial, examinaremos apenas o gerenciamento de transações usando ADO.NET. Em um tutorial futuro, veremos como usar procedimentos armazenados na Camada de Acesso a Dados, momento em que exploraremos as instruções SQL para criar, reverter e confirmar transações. Enquanto isso, consulte Gerenciando transações em SQL Server procedimentos armazenados para obter mais informações.

Observação

A TransactionScope classe no namespace permite que os System.Transactions desenvolvedores encapsulem programaticamente uma série de instruções dentro do escopo de uma transação e inclui suporte para transações complexas que envolvem várias fontes, como dois bancos de dados diferentes ou até mesmo tipos heterogêneos de armazenamentos de dados, como um banco de dados do Microsoft SQL Server, um banco de dados Oracle e um serviço Web. Decidi usar ADO.NET transações para este tutorial em vez da TransactionScope classe porque ADO.NET é mais específico para transações de banco de dados e, em muitos casos, é muito menos intensivo em recursos. Além disso, em determinados cenários, a TransactionScope classe usa o MSDTC (Coordenador de Transações Distribuídas da Microsoft). Os problemas de configuração, implementação e desempenho em torno do MSDTC o tornam um tópico bastante especializado e avançado e além do escopo desses tutoriais.

Ao trabalhar com o provedor SqlClient em ADO.NET, as transações são iniciadas por meio de uma chamada para o SqlConnection método da classe sBeginTransaction, que retorna um SqlTransaction objeto . As instruções de modificação de dados que compõem a transação são colocadas dentro de um try...catch bloco. Se ocorrer um erro em uma instrução no bloco , a try execução será transferida para o catch bloco em que a transação pode ser revertida por meio do SqlTransaction método do Rollbackobjeto. Se todas as instruções forem concluídas com êxito, uma chamada para o SqlTransaction método do Commit objeto no final do try bloco confirmará a transação. O snippet de código a seguir ilustra esse padrão.

' Create the SqlTransaction object
Dim myTransaction As SqlTransaction = SqlConnectionObject.BeginTransaction();
Try
    '
    ' ... Perform the database transaction�s data modification statements...
    '
    ' If we reach here, no errors, so commit the transaction
    myTransaction.Commit()
Catch
    ' If we reach here, there was an error, so rollback the transaction
    myTransaction.Rollback()
    Throw
End Try

Por padrão, os TableAdapters em um Conjunto de Dados Tipado não usam transações. Para fornecer suporte para transações, precisamos aumentar as classes TableAdapter para incluir métodos adicionais que usam o padrão acima para executar uma série de instruções de modificação de dados dentro do escopo de uma transação. Na Etapa 2, veremos como usar classes parciais para adicionar esses métodos.

Etapa 1: Criando o trabalho com páginas da Web de dados em lote

Antes de começarmos a explorar como aumentar o DAL para dar suporte a transações de banco de dados, primeiro vamos demorar um pouco para criar as ASP.NET páginas da Web que precisaremos para este tutorial e as três a seguir. Comece adicionando uma nova pasta chamada BatchData e, em seguida, adicione as páginas ASP.NET a seguir, associando cada página à Site.master página master.

  • Default.aspx
  • Transactions.aspx
  • BatchUpdate.aspx
  • BatchDelete.aspx
  • BatchInsert.aspx

Adicionar as páginas de ASP.NET para os tutoriais do SqlDataSource-Related

Figura 1: Adicionar as páginas de ASP.NET para os tutoriais do SqlDataSource-Related

Assim como acontece com as outras pastas, Default.aspx usará o SectionLevelTutorialListing.ascx Controle de Usuário para listar os tutoriais em sua seção. Portanto, adicione esse Controle de Usuário ao Default.aspx arrastando-o do Gerenciador de Soluções para o modo design da página.

Adicione o controle de usuário SectionLevelTutorialListing.ascx ao Default.aspx

Figura 2: Adicionar o controle de SectionLevelTutorialListing.ascx usuário a Default.aspx (clique para exibir a imagem em tamanho real)

Por fim, adicione essas quatro páginas como entradas ao Web.sitemap arquivo. Especificamente, adicione a seguinte marcação após a Personalização do Mapa <siteMapNode>do Site:

<siteMapNode title="Working with Batched Data" 
    url="~/BatchData/Default.aspx" 
    description="Learn how to perform batch operations as opposed to 
                 per-row operations.">
    
    <siteMapNode title="Adding Support for Transactions" 
        url="~/BatchData/Transactions.aspx" 
        description="See how to extend the Data Access Layer to support 
                     database transactions." />
    <siteMapNode title="Batch Updating" 
        url="~/BatchData/BatchUpdate.aspx" 
        description="Build a batch updating interface, where each row in a 
                      GridView is editable." />
    <siteMapNode title="Batch Deleting" 
        url="~/BatchData/BatchDelete.aspx" 
        description="Explore how to create an interface for batch deleting 
                     by adding a CheckBox to each GridView row." />
    <siteMapNode title="Batch Inserting" 
        url="~/BatchData/BatchInsert.aspx" 
        description="Examine the steps needed to create a batch inserting 
                     interface, where multiple records can be created at the 
                     click of a button." />
</siteMapNode>

Depois de atualizar Web.sitemap, reserve um momento para exibir o site de tutoriais por meio de um navegador. O menu à esquerda agora inclui itens para o trabalho com tutoriais de dados em lote.

O mapa do site agora inclui entradas para os tutoriais trabalhando com dados em lote

Figura 3: o mapa do site agora inclui entradas para os tutoriais trabalhando com dados em lote

Etapa 2: Atualizando a camada de acesso a dados para dar suporte a transações de banco de dados

Como discutimos no primeiro tutorial, Criando uma camada de acesso a dados, o DataSet tipado em nosso DAL é composto por DataTables e TableAdapters. Os DataTables contêm dados enquanto os TableAdapters fornecem a funcionalidade de ler dados do banco de dados no DataTables, atualizar o banco de dados com alterações feitas nas DataTables e assim por diante. Lembre-se de que os TableAdapters fornecem dois padrões para atualizar dados, que eu referi como Atualização em Lote e DB-Direct. Com o padrão de Atualização em Lote, o TableAdapter é passado um DataSet, DataTable ou uma coleção de DataRows. Esses dados são enumerados e, para cada linha inserida, modificada ou excluída, o InsertCommand, UpdateCommandou DeleteCommand é executado. Com o padrão DB-Direct, o TableAdapter é passado os valores das colunas necessárias para inserir, atualizar ou excluir um único registro. Em seguida, o método de padrão direto do BD usa esses valores passados para executar a instrução , UpdateCommandou DeleteCommand apropriadaInsertCommand.

Independentemente do padrão de atualização usado, os métodos gerados automaticamente por TableAdapters não usam transações. Por padrão, cada inserção, atualização ou exclusão executada pelo TableAdapter é tratada como uma única operação discreta. Por exemplo, imagine que o padrão de DB-Direct é usado por algum código na BLL para inserir dez registros no banco de dados. Esse código chamaria o método TableAdapter dez Insert vezes. Se as cinco primeiras inserções forem bem-sucedidas, mas a sexta resultar em uma exceção, os cinco primeiros registros inseridos permanecerão no banco de dados. Da mesma forma, se o padrão de Atualização em Lote for usado para executar inserções, atualizações e exclusões nas linhas inseridas, modificadas e excluídas em uma DataTable, se as primeiras várias modificações tiverem sido bem-sucedidas, mas uma posterior encontrar um erro, as modificações anteriores concluídas permanecerão no banco de dados.

Em determinados cenários, queremos garantir a atomicidade em uma série de modificações. Para fazer isso, devemos estender manualmente o TableAdapter adicionando novos métodos que executam o InsertCommand, UpdateCommande DeleteCommand s sob o guarda-chuva de uma transação. Em Criando uma camada de acesso a dados , examinamos o uso de classes parciais para estender a funcionalidade das DataTables dentro do Conjunto de Dados Tipado. Essa técnica também pode ser usada com TableAdapters.

O Conjunto de Dados Northwind.xsd Tipado está localizado na App_Code subpasta da DAL pasta. Crie uma subpasta na DAL pasta chamada TransactionSupport e adicione um novo arquivo de classe chamado ProductsTableAdapter.TransactionSupport.vb (consulte a Figura 4). Esse arquivo manterá a implementação parcial do ProductsTableAdapter que inclui métodos para executar modificações de dados usando uma transação.

Adicionar uma pasta chamada TransactionSupport e um arquivo de classe chamado ProductsTableAdapter.TransactionSupport.vb

Figura 4: Adicionar uma pasta chamada TransactionSupport e um arquivo de classe chamado ProductsTableAdapter.TransactionSupport.vb

Insira o seguinte código no ProductsTableAdapter.TransactionSupport.vb arquivo:

Imports System.Data
Imports System.Data.SqlClient
Namespace NorthwindTableAdapters
    Partial Public Class ProductsTableAdapter
        Private _transaction As SqlTransaction
        Private Property Transaction() As SqlTransaction
            Get
                Return Me._transaction
            End Get
            Set(ByVal Value As SqlTransaction)
                Me._transaction = Value
            End Set
        End Property
        Public Sub BeginTransaction()
            ' Open the connection, if needed
            If Me.Connection.State <> ConnectionState.Open Then
                Me.Connection.Open()
            End If
            ' Create the transaction and assign it to the Transaction property
            Me.Transaction = Me.Connection.BeginTransaction()
            ' Attach the transaction to the Adapters
            For Each command As SqlCommand In Me.CommandCollection
                command.Transaction = Me.Transaction
            Next
            Me.Adapter.InsertCommand.Transaction = Me.Transaction
            Me.Adapter.UpdateCommand.Transaction = Me.Transaction
            Me.Adapter.DeleteCommand.Transaction = Me.Transaction
        End Sub
        Public Sub CommitTransaction()
            ' Commit the transaction
            Me.Transaction.Commit()
            ' Close the connection
            Me.Connection.Close()
        End Sub
        Public Sub RollbackTransaction()
            ' Rollback the transaction
            Me.Transaction.Rollback()
            ' Close the connection
            Me.Connection.Close()
        End Sub
    End Class
End Namespace

O Partial palavra-chave na declaração de classe aqui indica ao compilador que os membros adicionados em devem ser adicionados à ProductsTableAdapter classe no NorthwindTableAdapters namespace . Observe a instrução Imports System.Data.SqlClient na parte superior do arquivo. Como o TableAdapter foi configurado para usar o provedor SqlClient, ele usa internamente um SqlDataAdapter objeto para emitir seus comandos para o banco de dados. Consequentemente, precisamos usar a SqlTransaction classe para iniciar a transação e, em seguida, confirmá-la ou revertê-la. Se você estiver usando um armazenamento de dados diferente do Microsoft SQL Server, precisará usar o provedor apropriado.

Esses métodos fornecem os blocos de construção necessários para iniciar, reverter e confirmar uma transação. Eles são marcados como Public, permitindo que sejam usados de dentro do ProductsTableAdapter, de outra classe no DAL ou de outra camada na arquitetura, como a BLL. BeginTransactionabre o TableAdapter interno SqlConnection (se necessário), inicia a transação e a Transaction atribui à propriedade e anexa a transação aos objetos s SqlCommand internosSqlDataAdapter. CommitTransactione RollbackTransaction chamam os Transaction métodos e Rollback do Commit objeto , respectivamente, antes de fechar o objeto internoConnection.

Etapa 3: Adicionar métodos para atualizar e excluir dados sob o guarda-chuva de uma transação

Com esses métodos concluídos, estamos prontos para adicionar métodos ou ProductsDataTable a BLL que executam uma série de comandos sob o guarda-chuva de uma transação. O método a seguir usa o padrão de Atualização em Lote para atualizar uma ProductsDataTable instância usando uma transação. Ele inicia uma transação chamando o BeginTransaction método e, em seguida, usa um Try...Catch bloco para emitir as instruções de modificação de dados. Se a chamada para o Adapter método do objeto resultar Update em uma exceção, a execução será transferida para o catch bloco em que a transação será revertida e a exceção será gerada novamente. Lembre-se de que o Update método implementa o padrão de Atualização em Lote enumerando as linhas do fornecido ProductsDataTable e executando os , UpdateCommande DeleteCommand s necessáriosInsertCommand. Se qualquer um desses comandos resultar em um erro, a transação será revertida, desfazendo as modificações anteriores feitas durante o tempo de vida da transação. Se a instrução Update for concluída sem erro, a transação será confirmada em sua totalidade.

Public Function UpdateWithTransaction _
    (ByVal dataTable As Northwind.ProductsDataTable) As Integer
    
    Me.BeginTransaction()
    Try
        ' Perform the update on the DataTable
        Dim returnValue As Integer = Me.Adapter.Update(dataTable)
        ' If we reach here, no errors, so commit the transaction
        Me.CommitTransaction()
        Return returnValue
    Catch
        ' If we reach here, there was an error, so rollback the transaction
        Me.RollbackTransaction()
        Throw
    End Try
End Function

Adicione o UpdateWithTransaction método à ProductsTableAdapter classe por meio da classe parcial em ProductsTableAdapter.TransactionSupport.vb. Como alternativa, esse método pode ser adicionado à classe da Camada de ProductsBLL Lógica de Negócios com algumas pequenas alterações sintáticas. Ou seja, o palavra-chave em , e precisaria ser substituído Adapter por (lembre-se de que Adapter é o nome de uma propriedade no ProductsBLL tipo ProductsTableAdapter).Me.RollbackTransaction()Me.CommitTransaction()Me.BeginTransaction()Me

O UpdateWithTransaction método usa o padrão de Atualização em Lote, mas uma série de chamadas DB-Direct também podem ser usadas dentro do escopo de uma transação, como mostra o método a seguir. O DeleteProductsWithTransaction método aceita como entrada um List(Of T) do tipo Integer, que são os ProductID s a serem excluídos. O método inicia a transação por meio de uma chamada para BeginTransaction e, em seguida, no Try bloco, itera pela lista fornecida chamando o método padrão Delete DB-Direct para cada ProductID valor. Se qualquer uma das chamadas para Delete falhar, o controle será transferido para o Catch bloco em que a transação é revertida e a exceção é lançada novamente. Se todas as chamadas forem Delete bem-sucedidas, a transação será confirmada. Adicione esse método à ProductsBLL classe .

Public Sub DeleteProductsWithTransaction _
    (ByVal productIDs As System.Collections.Generic.List(Of Integer))
    
    ' Start the transaction
    Adapter.BeginTransaction()
    Try
        ' Delete each product specified in the list
        For Each productID As Integer In productIDs
            Adapter.Delete(productID)
        Next
        ' Commit the transaction
        Adapter.CommitTransaction()
    Catch
        ' There was an error - rollback the transaction
        Adapter.RollbackTransaction()
        Throw
    End Try
End Sub

Aplicando transações em vários TableAdapters

O código relacionado à transação examinado neste tutorial permite que várias instruções em relação ProductsTableAdapter ao sejam tratadas como uma operação atômica. Mas e se várias modificações em tabelas de banco de dados diferentes precisarem ser executadas atomicamente? Por exemplo, ao excluir uma categoria, talvez primeiro queiramos reatribuir seus produtos atuais para alguma outra categoria. Essas duas etapas reatribuindo os produtos e excluindo a categoria devem ser executadas como uma operação atômica. Mas o ProductsTableAdapter inclui apenas métodos para modificar a Products tabela e inclui CategoriesTableAdapter apenas métodos para modificar a Categories tabela. Então, como uma transação pode abranger os dois TableAdapters?

Uma opção é adicionar um método ao CategoriesTableAdapter nomeado DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) e fazer com que esse método chame um procedimento armazenado que reatribua os produtos e exclua a categoria dentro do escopo de uma transação definida dentro do procedimento armazenado. Examinaremos como iniciar, confirmar e reverter transações em procedimentos armazenados em um tutorial futuro.

Outra opção é criar uma classe auxiliar no DAL que contém o DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) método . Esse método criaria uma instância do CategoriesTableAdapter e do ProductsTableAdapter e, em seguida, definiria essas duas propriedades TableAdapters Connection para a mesma SqlConnection instância. Nesse ponto, um dos dois TableAdapters iniciaria a transação com uma chamada para BeginTransaction. Os métodos TableAdapters para reatribuir os produtos e excluir a categoria seriam invocados em um Try...Catch bloco com a transação confirmada ou revertida conforme necessário.

Etapa 4: Adicionar oUpdateWithTransactionmétodo à camada de lógica de negócios

Na Etapa 3, adicionamos um UpdateWithTransaction método ao ProductsTableAdapter no DAL. Devemos adicionar um método correspondente à BLL. Embora a Camada de Apresentação possa chamar diretamente para o DAL para invocar o UpdateWithTransaction método , esses tutoriais têm se esforçado para definir uma arquitetura em camadas que isola o DAL da Camada de Apresentação. Portanto, cabe a nós continuar essa abordagem.

Abra o ProductsBLL arquivo de classe e adicione um método chamado UpdateWithTransaction que simplesmente chama para baixo para o método DAL correspondente. Agora deve haver dois novos métodos em ProductsBLL: UpdateWithTransaction, que você acabou de adicionar e DeleteProductsWithTransaction, que foi adicionado na Etapa 3.

Public Function UpdateWithTransaction _
    (ByVal products As Northwind.ProductsDataTable) As Integer
    
    Return Adapter.UpdateWithTransaction(products)
End Function
Public Sub DeleteProductsWithTransaction _
    (ByVal productIDs As System.Collections.Generic.List(Of Integer))
    
    ' Start the transaction
    Adapter.BeginTransaction()
    Try
        ' Delete each product specified in the list
        For Each productID As Integer In productIDs
            Adapter.Delete(productID)
        Next
        ' Commit the transaction
        Adapter.CommitTransaction()
    Catch
        ' There was an error - rollback the transaction
        Adapter.RollbackTransaction()
        Throw
    End Try
End Sub

Observação

Esses métodos não incluem o DataObjectMethodAttribute atributo atribuído à maioria dos ProductsBLL outros métodos na classe porque invocaremos esses métodos diretamente das classes code-behind das páginas ASP.NET. Lembre-se de que DataObjectMethodAttribute é usado para sinalizar quais métodos devem aparecer no assistente Configurar Fonte de Dados do ObjectDataSource e em qual guia (SELECT, UPDATE, INSERT ou DELETE). Como o GridView não tem suporte interno para edição ou exclusão em lote, teremos que invocar esses métodos programaticamente em vez de usar a abordagem declarativa sem código.

Etapa 5: Atualizar atomicamente dados de banco de dados da camada de apresentação

Para ilustrar o efeito que a transação tem ao atualizar um lote de registros, vamos criar uma interface do usuário que lista todos os produtos em um GridView e inclui um controle Web button que, quando clicado, reatribui os valores dos produtos CategoryID . Em particular, a reatribuição da categoria progredirá para que os primeiros vários produtos sejam atribuídos a um valor válido CategoryID , enquanto outros recebem propositalmente um valor inexistente CategoryID . Se tentarmos atualizar o banco de dados com um produto cuja CategoryID não corresponda a uma categoria existente s CategoryID, ocorrerá uma violação de restrição de chave estrangeira e uma exceção será gerada. O que veremos neste exemplo é que, ao usar uma transação, a exceção gerada da violação de restrição de chave estrangeira fará com que as alterações válidas CategoryID anteriores sejam revertidas. No entanto, quando não estiver usando uma transação, as modificações nas categorias iniciais permanecerão.

Comece abrindo a Transactions.aspx página na BatchData pasta e arraste um GridView da Caixa de Ferramentas para o Designer. Defina como IDProducts e, de sua marca inteligente, associe-o a um novo ObjectDataSource chamado ProductsDataSource. Configure o ObjectDataSource para efetuar pull de seus dados do ProductsBLL método da classe s GetProducts . Esse será um GridView somente leitura, portanto, defina as listas suspensas nas guias UPDATE, INSERT e DELETE como (Nenhum) e clique em Concluir.

Configurar o ObjectDataSource para usar o método GetProducts da classe ProductsBLL

Figura 5: Configurar o ObjectDataSource para usar o ProductsBLL método da classe (GetProductsclique para exibir a imagem em tamanho real)

Defina o Drop-Down Listas nas guias UPDATE, INSERT e DELETE como (Nenhum)

Figura 6: Definir o Drop-Down Listas nas guias UPDATE, INSERT e DELETE como (Nenhum) (Clique para exibir a imagem em tamanho real)

Depois de concluir o assistente Configurar Fonte de Dados, o Visual Studio criará BoundFields e um CheckBoxField para os campos de dados do produto. Remova todos esses campos, exceto ProductID, ProductName, CategoryIDe CategoryName renomeie as ProductName propriedades e CategoryName BoundFields HeaderText como Product e Category, respectivamente. Na marca inteligente, marcar a opção Habilitar Paginação. Depois de fazer essas modificações, a marcação declarativa de GridView e ObjectDataSource deve ser semelhante à seguinte:

<asp:GridView ID="Products" runat="server" AllowPaging="True" 
    AutoGenerateColumns="False" DataKeyNames="ProductID" 
    DataSourceID="ProductsDataSource">
    <Columns>
        <asp:BoundField DataField="ProductID" HeaderText="ProductID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" HeaderText="Product" 
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>

Em seguida, adicione três controles da Web de botão acima do GridView. Defina a primeira propriedade Texto do Botão como Grade de Atualização, a segunda como Modificar Categorias (WITH TRANSACTION) e a terceira como Modificar Categorias (SEM TRANSAÇÃO).

<p>
    <asp:Button ID="RefreshGrid" runat="server" Text="Refresh Grid" />
</p>
<p>
    <asp:Button ID="ModifyCategoriesWithTransaction" runat="server"
        Text="Modify Categories (WITH TRANSACTION)" />
</p>
<p>
    <asp:Button ID="ModifyCategoriesWithoutTransaction" runat="server"
        Text="Modify Categories (WITHOUT TRANSACTION)" />
</p>

Neste ponto, o modo design no Visual Studio deve ser semelhante à captura de tela mostrada na Figura 7.

A página contém um GridView e três controles web de botão

Figura 7: a página contém um GridView e três controles da Web de botão (clique para exibir a imagem em tamanho real)

Crie manipuladores de eventos para cada um dos três eventos do Click Botão e use o seguinte código:

Protected Sub RefreshGrid_Click _
    (ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles RefreshGrid.Click
    
    Products.DataBind()
End Sub
Protected Sub ModifyCategoriesWithTransaction_Click _
    (ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles ModifyCategoriesWithTransaction.Click
    
    ' Get the set of products
    Dim productsAPI As New ProductsBLL()
    Dim productsData As Northwind.ProductsDataTable = productsAPI.GetProducts()
    ' Update each product's CategoryID
    For Each product As Northwind.ProductsRow In productsData
        product.CategoryID = product.ProductID
    Next
    ' Update the data using a transaction
    productsAPI.UpdateWithTransaction(productsData)
    ' Refresh the Grid
    Products.DataBind()
End Sub
Protected Sub ModifyCategoriesWithoutTransaction_Click _
    (ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles ModifyCategoriesWithoutTransaction.Click
    
    ' Get the set of products
    Dim productsAPI As New ProductsBLL()
    Dim productsData As Northwind.ProductsDataTable = productsAPI.GetProducts()
    ' Update each product's CategoryID
    For Each product As Northwind.ProductsRow In productsData
        product.CategoryID = product.ProductID
    Next
    ' Update the data WITHOUT using a transaction
    Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
    productsAdapter.Update(productsData)
    ' Refresh the Grid
    Products.DataBind()
End Sub

O manipulador de eventos do Click Botão de atualização simplesmente reassocia os dados ao GridView chamando o Products método GridView s DataBind .

O segundo manipulador de eventos reatribui os produtos CategoryID s e usa o novo método de transação da BLL para executar as atualizações de banco de dados sob o guarda-chuva de uma transação. Observe que cada produto é CategoryID arbitrariamente definido com o mesmo valor que seu ProductID. Isso funcionará bem para os primeiros produtos, pois esses produtos têm ProductID valores que são mapeados para s válidos CategoryID . Mas uma vez que os ProductID s começam a ficar muito grandes, essa sobreposição coincidente de ProductID s e CategoryID s não se aplica mais.

O terceiro Click manipulador de eventos atualiza os produtos CategoryID da mesma maneira, mas envia a atualização para o banco de dados usando o ProductsTableAdapter método padrão Update s. Esse Update método não encapsula a série de comandos dentro de uma transação, portanto, essas alterações são feitas antes do primeiro erro de violação de restrição de chave estrangeira encontrado persistir.

Para demonstrar esse comportamento, visite esta página por meio de um navegador. Inicialmente, você deve ver a primeira página de dados, conforme mostrado na Figura 8. Em seguida, clique no botão Modificar Categorias (WITH TRANSACTION). Isso causará um postback e tentará atualizar todos os valores de produtos CategoryID , mas resultará em uma violação de restrição de chave estrangeira (consulte a Figura 9).

Os produtos são exibidos em um GridView paginável

Figura 8: Os produtos são exibidos em um GridView paginável (clique para exibir a imagem em tamanho real)

Reatribuir os resultados das categorias em uma violação de restrição de chave estrangeira

Figura 9: Reatribuir os resultados das categorias em uma violação de restrição de chave estrangeira (clique para exibir a imagem em tamanho real)

Agora, pressione o botão Voltar do navegador e clique no botão Atualizar Grade. Ao atualizar os dados, você deverá ver exatamente a mesma saída mostrada na Figura 8. Ou seja, embora alguns dos produtos CategoryID tenham sido alterados para valores legais e atualizados no banco de dados, eles foram revertidos quando ocorreu a violação da restrição de chave estrangeira.

Agora tente clicar no botão Modificar Categorias (SEM TRANSAÇÃO). Isso resultará no mesmo erro de violação de restrição de chave estrangeira (consulte a Figura 9), mas desta vez os produtos cujos CategoryID valores foram alterados para um valor legal não serão revertidos. Pressione o botão Voltar do navegador e, em seguida, o botão Atualizar Grade. Como mostra a Figura 10, os CategoryID s dos oito primeiros produtos foram reatribuídos. Por exemplo, na Figura 8, Chang tinha um CategoryID de 1, mas na Figura 10 ele foi reatribuído a 2.

Alguns valores categoryID de produtos foram atualizados enquanto outros não foram

Figura 10: Alguns valores de produtos CategoryID foram atualizados enquanto outros não foram (clique para exibir imagem em tamanho real)

Resumo

Por padrão, os métodos tableAdapter não encapsulam as instruções de banco de dados executadas dentro do escopo de uma transação, mas com um pouco de trabalho podemos adicionar métodos que criarão, confirmarão e reverterão uma transação. Neste tutorial, criamos três desses métodos na ProductsTableAdapter classe : BeginTransaction, CommitTransactione RollbackTransaction. Vimos como usar esses métodos junto com um Try...Catch bloco para tornar atômicas uma série de instruções de modificação de dados. Em particular, criamos o UpdateWithTransaction método no ProductsTableAdapter, que usa o padrão de Atualização em Lote para executar as modificações necessárias nas linhas de um fornecido ProductsDataTable. Também adicionamos o DeleteProductsWithTransaction método à ProductsBLL classe na BLL, que aceita um List de ProductID valores como sua entrada e chama o método Delete padrão DB-Direct para cada ProductID. Ambos os métodos começam criando uma transação e, em seguida, executando as instruções de modificação de dados dentro de um Try...Catch bloco. Se ocorrer uma exceção, a transação será revertida; caso contrário, ela será confirmada.

A etapa 5 ilustrava o efeito das atualizações em lotes transacionais versus atualizações em lote que não usavam uma transação. Nos próximos três tutoriais, vamos nos basear neste tutorial e criar interfaces do usuário para executar atualizações, exclusões e inserções em lote.

Programação feliz!

Leitura Adicional

Para obter mais informações sobre os tópicos discutidos neste tutorial, consulte os seguintes recursos:

Sobre o autor

Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.

Agradecimentos Especiais

Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Dave Gardner, Hilton Giesenow e Teresa Murphy. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, deixe-me uma linha em mitchell@4GuysFromRolla.com.