Encapsulamento de modificações de banco de dados em uma transação (C#)
por Scott Mitchell
Este tutorial é o primeiro de quatro que analisam a atualização, exclusão e inserção de lotes de dados. Neste tutorial, aprendemos como as transações de banco de dados permitem que 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 a partir do tutorial Uma visão geral da 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 rica de modificação de dados sem escrever uma linha de código, desde que você se contente em editar e excluir 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 do 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, em seguida, 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, em seguida, 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 sejam bem-sucedidas 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 lote seja tratada como uma operação atômica, em que todas as etapas são bem-sucedidas ou todas as etapas falham, a Camada de Acesso a Dados precisa 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 INSERT
instruções , UPDATE
e DELETE
executadas sob o guarda-chuva da transação e são um recurso suportado pela maioria dos sistemas de banco de dados modernos.
Neste tutorial, veremos como estender a DAL para usar transações de banco de dados. Os tutoriais subsequentes examinarão a implementação de páginas da Web para inserção, atualização e exclusão de 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 que algumas modificações de dados sejam bem-sucedidas e outras no mesmo lote falhem, 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 os registros processados sem erro permaneçam excluídos. Nesses casos, a DAL não precisa ser modificada para dar suporte a transações de banco de dados. No entanto, há outros cenários de operação em lote, em que a atomicidade é vital. Quando um cliente transfere seus fundos de uma conta bancária para outra, duas operações devem ser realizadas: os fundos devem ser deduzidos da primeira conta e depois adicionados à segunda. 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 na DAL para dar suporte a transações de banco de dados, mesmo que você não planeje usá-los nas interfaces de inserção, atualização e exclusão em lote 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, o que permite 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 serão bem-sucedidos.
Em geral, as transações são implementadas por meio de instruções SQL usando o seguinte padrão:
- Indique o início de uma transação.
- Execute as instruções SQL que compõem a transação.
- Se houver um erro em uma das instruções da Etapa 2, reverta a transação.
- 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.
Observação
A TransactionScope
classe no System.Transactions
namespace permite que os 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 da Web. Decidi usar transações ADO.NET 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, consome muito menos 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 no ADO.NET, as transações são iniciadas por meio de uma chamada para o SqlConnection
método da BeginTransaction
classe, 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 execução será transferida try
para o catch
bloco em que a transação pode ser revertida por meio do SqlTransaction
método do Rollback
objeto. 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. Consulte Mantendo a consistência do banco de dados com transações.
// Create the SqlTransaction object
SqlTransaction myTransaction = 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;
}
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 suportar transações de banco de dados, vamos primeiro reservar um momento para criar as ASP.NET páginas da Web que precisaremos para este tutorial e as três que se seguem. Comece adicionando uma nova pasta chamada BatchData
e, em seguida, adicione as seguintes páginas ASP.NET, associando cada página à Site.master
página mestra.
Default.aspx
Transactions.aspx
BatchUpdate.aspx
BatchDelete.aspx
BatchInsert.aspx
Figura 1: Adicionar as páginas ASP.NET para os tutoriais relacionados ao SqlDataSource
Tal como acontece com as outras pastas, Default.aspx
usará o SectionLevelTutorialListing.ascx
Controle de Usuário para listar os tutoriais dentro de sua seção. Portanto, adicione esse Controle de Usuário arrastando-o Default.aspx
do Gerenciador de Soluções para o modo de exibição Design da página.
Figura 2: Adicionar o controle de usuário a Default.aspx
(clique para exibir a SectionLevelTutorialListing.ascx
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>
Após a atualização Web.sitemap
, reserve um momento para visualizar o site de tutoriais por meio de um navegador. O menu à esquerda agora inclui itens para o trabalho com tutoriais de 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 nossa DAL é composto por DataTables e TableAdapters. As DataTables contêm dados, enquanto os TableAdapters fornecem a funcionalidade para ler dados do banco de dados para as DataTables, para 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 chamei de Atualização em Lote e DB-Direct. Com o padrão de Atualização em Lote, o TableAdapter recebe 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
, UpdateCommand
, ou DeleteCommand
é executado. Com o padrão DB-Direct, o TableAdapter recebe os valores das colunas necessárias para inserir, atualizar ou excluir um único registro. O método de padrão DB Direct usa esses valores passados para executar a instrução , UpdateCommand
, ou DeleteCommand
apropriadaInsertCommand
.
Independentemente do padrão de atualização usado, os métodos gerados automaticamente 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 DB-Direct seja usado por algum código na BLL para inserir dez registros no banco de dados. Esse código chamaria o método do TableAdapter Insert
dez 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 modificações forem 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
, UpdateCommand
, e 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 Northwind.xsd
de Dados Tipado está localizado na App_Code
subpasta da DAL
pasta. Crie uma subpasta na pasta chamada TransactionSupport
e adicione um novo arquivo de classe chamado ProductsTableAdapter.TransactionSupport.cs
(consulte a DAL
Figura 4). Este arquivo conterá a implementação parcial do que inclui métodos para realizar modificações de ProductsTableAdapter
dados usando uma transação.
Figura 4: Adicionar uma pasta chamada TransactionSupport
e um arquivo de classe chamado ProductsTableAdapter.TransactionSupport.cs
Insira o seguinte código no ProductsTableAdapter.TransactionSupport.cs
arquivo:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace NorthwindTableAdapters
{
public partial class ProductsTableAdapter
{
private SqlTransaction _transaction;
private SqlTransaction Transaction
{
get
{
return this._transaction;
}
set
{
this._transaction = value;
}
}
public void BeginTransaction()
{
// Open the connection, if needed
if (this.Connection.State != ConnectionState.Open)
this.Connection.Open();
// Create the transaction and assign it to the Transaction property
this.Transaction = this.Connection.BeginTransaction();
// Attach the transaction to the Adapters
foreach (SqlCommand command in this.CommandCollection)
{
command.Transaction = this.Transaction;
}
this.Adapter.InsertCommand.Transaction = this.Transaction;
this.Adapter.UpdateCommand.Transaction = this.Transaction;
this.Adapter.DeleteCommand.Transaction = this.Transaction;
}
public void CommitTransaction()
{
// Commit the transaction
this.Transaction.Commit();
// Close the connection
this.Connection.Close();
}
public void RollbackTransaction()
{
// Rollback the transaction
this.Transaction.Rollback();
// Close the connection
this.Connection.Close();
}
}
}
A partial
palavra-chave na declaração de classe aqui indica ao compilador que os membros adicionados devem ser adicionados à ProductsTableAdapter
classe no NorthwindTableAdapters
namespace. Observe a using System.Data.SqlClient
instrução na parte superior do arquivo. Como o TableAdapter foi configurado para usar o provedor SqlClient, internamente ele usa 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 public
como , permitindo que sejam usados de dentro do ProductsTableAdapter
, de outra classe no DAL ou de outra camada na arquitetura, como a BLL. BeginTransaction
abre o TableAdapter interno SqlConnection
(se necessário), inicia a transação e a atribui à Transaction
propriedade e anexa a transação aos objetos internos SqlDataAdapter
SqlCommand
do s. CommitTransaction
e chame RollbackTransaction
o objeto s Commit
e Rollback
métodosTransaction
, respectivamente, antes de fechar o objeto internoConnection
.
Etapa 3: Adicionando 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 à ProductsDataTable
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 Update
objeto resultar 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á lançada 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 o , UpdateCommand
e 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 Update
declaração for concluída sem erros, a transação será confirmada em sua totalidade.
public int UpdateWithTransaction(Northwind.ProductsDataTable dataTable)
{
this.BeginTransaction();
try
{
// Perform the update on the DataTable
int returnValue = this.Adapter.Update(dataTable);
// If we reach here, no errors, so commit the transaction
this.CommitTransaction();
return returnValue;
}
catch
{
// If we reach here, there was an error, so rollback the transaction
this.RollbackTransaction();
throw;
}
}
Adicione o UpdateWithTransaction
método à ProductsTableAdapter
classe por meio da classe parcial em ProductsTableAdapter.TransactionSupport.cs
. Como alternativa, esse método pode ser adicionado à classe da ProductsBLL
Camada Lógica de Negócios com algumas pequenas alterações sintáticas. Ou seja, a palavra-chave this in this.BeginTransaction()
, , e this.RollbackTransaction()
precisaria ser substituída por Adapter
(lembre-se de que Adapter
é o nome de uma propriedade do ProductsBLL
tipo ProductsTableAdapter
this.CommitTransaction()
).
O UpdateWithTransaction
método usa o padrão de atualização em lote, mas uma série de chamadas diretas de banco de dados também pode ser usada dentro do escopo de uma transação, como mostra o método a seguir. O DeleteProductsWithTransaction
método aceita como entrada a do List<T>
tipo int
, 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 por meio da lista fornecida chamando o método de padrão Delete
DB-Direct para cada ProductID
valor. Se qualquer uma das chamadas falhar Delete
, 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 void DeleteProductsWithTransaction
(System.Collections.Generic.List<int> productIDs)
{
// Start the transaction
Adapter.BeginTransaction();
try
{
// Delete each product specified in the list
foreach (int productID in productIDs)
{
Adapter.Delete(productID);
}
// Commit the transaction
Adapter.CommitTransaction();
}
catch
{
// There was an error - rollback the transaction
Adapter.RollbackTransaction();
throw;
}
}
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 ao ProductsTableAdapter
sejam tratadas como uma operação atômica. Mas e se várias modificações em diferentes tabelas de banco de dados precisarem ser executadas atomicamente? Por exemplo, ao excluir uma categoria, podemos primeiro querer reatribuir seus produtos atuais a alguma outra categoria. Essas duas etapas de reatribuição dos produtos e exclusão da 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 ambos os 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 no procedimento armazenado. Veremos como iniciar, confirmar e reverter transações em procedimentos armazenados em um tutorial futuro.
Outra opção é criar uma classe auxiliar na DAL que contém o DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)
método. Esse método criaria uma instância de e CategoriesTableAdapter
o ProductsTableAdapter
e, em seguida, definiria essas duas propriedades TableAdapters Connection
para a mesma SqlConnection
instância. Nesse ponto, qualquer 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: Adicionando oUpdateWithTransaction
método à camada de lógica de negócios
Na Etapa 3, ProductsTableAdapter
adicionamos um UpdateWithTransaction
método ao no DAL. Devemos adicionar um método correspondente à BLL. Embora a Camada de Apresentação possa chamar diretamente a DAL para invocar o UpdateWithTransaction
método, esses tutoriais se esforçaram para definir uma arquitetura em camadas que isola a DAL da Camada de Apresentação. Portanto, cabe a nós continuar essa abordagem.
Abra o arquivo de ProductsBLL
classe e adicione um método chamado UpdateWithTransaction
que simplesmente chama 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 int UpdateWithTransaction(Northwind.ProductsDataTable products)
{
return Adapter.UpdateWithTransaction(products);
}
public void DeleteProductsWithTransaction
(System.Collections.Generic.List<int> productIDs)
{
// Start the transaction
Adapter.BeginTransaction();
try
{
// Delete each product specified in the list
foreach (int productID in productIDs)
Adapter.Delete(productID);
// Commit the transaction
Adapter.CommitTransaction();
}
catch
{
// There was an error - rollback the transaction
Adapter.RollbackTransaction();
throw;
}
}
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 de 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: Atualizando atomicamente os dados do 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 de botão que, quando clicado, reatribui os valores dos produtos CategoryID
. Em particular, a reatribuição de categoria progredirá para que os primeiros produtos recebam um valor válido CategoryID
, enquanto outros recebem propositalmente um valor inexistente CategoryID
. Se tentarmos atualizar o banco de dados com um produto que CategoryID
não corresponda a uma categoria existente 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 pela violação da 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
BatchData
página na pasta e arraste um GridView da Caixa de Ferramentas para o Designer. Defina como ID
Products
e, em sua marca inteligente, associe-o a um novo ObjectDataSource chamado ProductsDataSource
. Configure o ObjectDataSource para extrair seus dados do ProductsBLL
método da GetProducts
classe. Esse será um GridView somente leitura, portanto, defina as listas suspensas nas guias UPDATE, INSERT e DELETE como (Nenhum) e clique em Concluir.
Figura 5: Figura 5: Configurar o ObjectDataSource para usar o método da GetProducts
classe (clique para exibir a ProductsBLL
imagem em tamanho real)
Figura 6: Defina as listas suspensas 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
, CategoryID
, e CategoryName
renomeie as ProductName
propriedades e CategoryName
BoundFields HeaderText
para Produto e Categoria, respectivamente. Na marca inteligente, marque a opção Ativar paginação. Depois de fazer essas modificações, a marcação declarativa do 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 Web de botão acima do GridView. Defina a propriedade Texto do primeiro Botão como Atualizar Grade, a segunda para Modificar Categorias (WITH TRANSACTION) e a terceira para Modificar Categorias (WITHOUT TRANSACTION) .
<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 de exibição Design no Visual Studio deve ser semelhante à captura de tela mostrada na Figura 7.
Figura 7: A página contém um GridView e três controles 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
Button e use o seguinte código:
protected void RefreshGrid_Click(object sender, EventArgs e)
{
Products.DataBind();
}
protected void ModifyCategoriesWithTransaction_Click(object sender, EventArgs e)
{
// Get the set of products
ProductsBLL productsAPI = new ProductsBLL();
Northwind.ProductsDataTable products = productsAPI.GetProducts();
// Update each product's CategoryID
foreach (Northwind.ProductsRow product in products)
{
product.CategoryID = product.ProductID;
}
// Update the data using a transaction
productsAPI.UpdateWithTransaction(products);
// Refresh the Grid
Products.DataBind();
}
protected void ModifyCategoriesWithoutTransaction_Click(object sender, EventArgs e)
{
// Get the set of products
ProductsBLL productsAPI = new ProductsBLL();
Northwind.ProductsDataTable products = productsAPI.GetProducts();
// Update each product's CategoryID
foreach (Northwind.ProductsRow product in products)
{
product.CategoryID = product.ProductID;
}
// Update the data WITHOUT using a transaction
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
productsAdapter.Update(products);
// Refresh the Grid
Products.DataBind();
}
O manipulador de eventos do Click
botão de atualização simplesmente reassocia os dados ao GridView chamando o Products
método do DataBind
GridView.
O segundo manipulador de eventos reatribui os produtos CategoryID
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
definido arbitrariamente 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 o ProductID
s começa a ficar muito grande, 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
s 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 envolve a série de comandos em uma transação, portanto, essas alterações são feitas antes que o primeiro erro de violação de restrição de chave estrangeira encontrado persista.
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 (COM TRANSAÇÃO). 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).
Figura 8: Os produtos são exibidos em um GridView paginável (clique para exibir a imagem em tamanho real)
Figura 9: A reatribuição das categorias resulta em uma violação de restrição de chave estrangeira (clique para exibir a imagem em tamanho real)
Agora aperte o botão Voltar do seu navegador e clique no botão Atualizar grade. Ao atualizar os dados, você deve 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. Aperte o botão Voltar do seu 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.
Figura 10: Alguns valores de produtos CategoryID
foram atualizados enquanto outros não foram (clique para exibir a imagem em tamanho real)
Resumo
Por padrão, os métodos do 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
, CommitTransaction
, e RollbackTransaction
. Vimos como usar esses métodos junto com um try...catch
bloco para tornar atômica 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 .ProductsDataTable
Também adicionamos o DeleteProductsWithTransaction
método à ProductsBLL
classe na BLL, que aceita um List
de valores como entrada e chama o método Delete
de ProductID
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 ilustrou o efeito de atualizações em lote transacionais versus atualizações em lote que negligenciaram o uso de uma transação. Nos próximos três tutoriais, criaremos interfaces de usuário para executar atualizações, exclusões e inserções em lote.
Boa programação!
Leitura Adicional
Para obter mais informações sobre os tópicos discutidos neste tutorial, consulte os seguintes recursos:
- Transações facilitadas:
System.Transactions
- TransactionScope e DataAdapters
- Usando transações do Oracle Database no .NET
Sobre o autor
Scott Mitchell, autor de sete livros ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Web da Microsoft desde 1998. Scott trabalha como consultor, instrutor e escritor independente. 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 a
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? Em caso afirmativo, envie-me uma mensagem para mitchell@4GuysFromRolla.com.