Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
por Scott Mitchell
Neste tutorial, analisamos o essencial do controle de simultaneidade otimista e, em seguida, exploramos como implementá-lo usando o controle SqlDataSource.
Introdução
No tutorial anterior, examinamos como adicionar recursos de inserção, atualização e exclusão ao controle SqlDataSource. Em suma, para fornecer esses recursos, precisávamos especificar a instrução INSERT, UPDATE ou DELETE SQL nas propriedades de controle InsertCommand, UpdateCommand ou DeleteCommand, juntamente com os parâmetros apropriados nas coleções InsertParameters, UpdateParameters e DeleteParameters. Embora essas propriedades e coleções possam ser especificadas manualmente, o botão Avançado do Assistente para Configurar Fonte de Dados oferece uma caixa de seleção Gerar INSERT, UPDATE, e DELETE instruções que criarão automaticamente essas instruções baseadas na SELECT instrução.
Juntamente com a caixa de seleção Gerar INSERT, UPDATE e DELETE instruções, a caixa de diálogo Opções Avançadas de Geração de SQL inclui uma opção Usar simultaneidade otimista (consulte a Figura 1). Quando verificadas, as cláusulas WHERE das instruções UPDATE e DELETE geradas automaticamente são modificadas para executar apenas a atualização ou exclusão se os dados subjacentes no banco de dados não tiverem sido modificados desde a última vez que o usuário carregou os dados na tabela.
Figura 1: Você pode adicionar suporte de simultaneidade otimista da caixa de diálogo Opções avançadas de geração de SQL
De volta ao tutorial implementando simultaneidade otimista , examinamos os conceitos básicos do controle de simultaneidade otimista e como adicioná-lo ao ObjectDataSource. Neste tutorial, retocaremos o essencial do controle de simultaneidade otimista e, em seguida, exploraremos como implementá-lo usando o SqlDataSource.
Um resumo da concorrência otimista
Para aplicativos web que permitem que vários usuários simultâneos editem ou excluam os mesmos dados, existe a possibilidade de um usuário substituir acidentalmente as alterações feitas por outro usuário. No tutorial Implementando Concorrência Otimista, forneci o seguinte exemplo:
Imagine que dois usuários, Jisun e Sam, estavam visitando uma página em um aplicativo que permitia aos visitantes atualizar e excluir produtos por meio de um controle GridView. Ambos clicam no botão Editar para Chai ao mesmo tempo. O Jisun altera o nome do produto para Chai Tea e clica no botão Atualizar. O resultado líquido é uma instrução UPDATE enviada ao banco de dados, que define todos os campos atualizáveis do produto (embora o Jisun tenha atualizado apenas um campo). ProductName Neste momento, o banco de dados tem os valores Chai Tea, a categoria Bebidas, o fornecedor Exotic Liquids e assim por diante para este produto específico. No entanto, o GridView na tela do Sam ainda mostra o nome do produto na linha GridView editável como Chai. Alguns segundos após as alterações do Jisun terem sido confirmadas, Sam atualiza a categoria para Condimentos e clica em Atualizar. Isso resulta em uma instrução UPDATE enviada ao banco de dados que define o nome do produto como Chai, e define o CategoryID como o ID correspondente da categoria Condimentos, e assim por diante. As alterações de Jisun no nome do produto foram substituídas.
A Figura 2 ilustra essa interação.
Figura 2: quando dois usuários atualizam simultaneamente um registro, há potencial para que as alterações de um usuário substituam as outras (clique para exibir a imagem em tamanho real)
Para impedir que esse cenário se desenvolva, uma forma de controle de concorrência precisa ser implementada. Simultaneidade otimista o foco deste tutorial funciona na suposição de que, embora possa haver conflitos de simultaneidade de vez em quando, a grande maioria das vezes tais conflitos não surgirão. Portanto, se ocorrer um conflito, o controle de simultaneidade otimista simplesmente informa ao usuário que suas alterações não podem ser salvas porque outro usuário modificou os mesmos dados.
Observação
Para aplicativos em que se supõe que haverá muitos conflitos de simultaneidade ou se esses conflitos não forem toleráveis, o controle de simultaneidade pessimista poderá ser usado. Consulte o tutorial implementando simultaneidade otimista para uma discussão mais completa sobre o controle de simultaneidade pessimista.
O controle de simultaneidade otimista funciona garantindo que o registro que está sendo atualizado ou excluído tenha os mesmos valores que quando o processo de atualização ou exclusão foi iniciado. Por exemplo, ao clicar no botão Editar em um GridView editável, os valores dos registros são lidos do banco de dados e exibidos em TextBoxes e outros controles da Web. Esses valores originais são salvos pelo GridView. Posteriormente, depois que o usuário fizer suas alterações e clicar no botão Atualizar, a UPDATE instrução usada deverá levar em conta os valores originais mais os novos valores e atualizar apenas o registro de banco de dados subjacente se os valores originais que o usuário começou a editar forem idênticos aos valores ainda no banco de dados. A Figura 3 ilustra essa sequência de eventos.
Figura 3: Para que a atualização ou exclusão seja bem-sucedida, os valores originais devem ser iguais aos valores atuais do banco de dados (clique para exibir a imagem em tamanho real)
Consulte a Lógica de Atualização de Concorrência Otimista de Peter A. Bromberg para uma breve olhada em várias opções. A técnica usada pelo SqlDataSource (bem como pelo ADO.NET Conjuntos de Dados Tipados usados em nossa Camada de Acesso a Dados) aumenta a WHERE cláusula para incluir uma comparação de todos os valores originais.
UPDATE A instrução a seguir, por exemplo, atualiza o nome e o preço de um produto somente se os valores atuais do banco de dados forem iguais aos valores que foram originalmente recuperados ao atualizar o registro no GridView. Os parâmetros @ProductName e @UnitPrice contêm os novos valores inseridos pelo usuário, enquanto @original_ProductName e @original_UnitPrice contêm os valores que foram originalmente carregados no GridView quando o botão Editar foi clicado.
UPDATE Products SET
ProductName = @ProductName,
UnitPrice = @UnitPrice
WHERE
ProductID = @original_ProductID AND
ProductName = @original_ProductName AND
UnitPrice = @original_UnitPrice
Como veremos neste tutorial, habilitar o controle de simultaneidade otimista com o SqlDataSource é tão simples quanto verificar uma caixa de seleção.
Etapa 1: Criando um SqlDataSource que dá suporte à simultaneidade otimista
Comece abrindo a OptimisticConcurrency.aspx página da SqlDataSource pasta. Arraste um controle SqlDataSource da Caixa de Ferramentas para o Designer, definindo sua ID propriedade como ProductsDataSourceWithOptimisticConcurrency. Em seguida, clique no link Configurar Fonte de Dados a partir do smart tag do controle. Na primeira tela do assistente, escolha trabalhar com o NORTHWINDConnectionString botão e clique em Avançar.
Figura 4: Escolha trabalhar com a NORTHWINDConnectionStringimagem (Clique para exibir a imagem em tamanho real)
Para este exemplo, adicionaremos um GridView que permite aos usuários editar a Products tabela. Portanto, na tela Configurar a Instrução Selecionar, escolha a tabela Products na lista suspensa e selecione as colunas ProductID, ProductName, UnitPrice e Discontinued, conforme mostrado na Figura 5.
Figura 5: Da Products tabela, retorne as colunas ProductID, ProductName, UnitPrice e Discontinued (clique para exibir a imagem em tamanho real)
Depois de escolher as colunas, clique no botão Avançado para abrir a caixa de diálogo Opções Avançadas de Geração de SQL. Verifique as instruções Gerar INSERT, UPDATE e DELETE, use as caixas de seleção de verificação de simultaneidade otimista e clique em OK (consulte a Figura 1 para ver uma captura de tela). Conclua o assistente clicando em Avançar e, em seguida, Concluir.
Depois de concluir o assistente Configurar Fonte de Dados, reserve um momento para examinar as propriedades resultantes DeleteCommand e UpdateCommand e as coleções DeleteParameters e UpdateParameters. A maneira mais fácil de fazer isso é clicar na guia Origem no canto inferior esquerdo para ver a sintaxe declarativa da página. Lá, você encontrará um UpdateCommand valor de:
UPDATE [Products] SET
[ProductName] = @ProductName,
[UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
[UnitPrice] = @original_UnitPrice AND
[Discontinued] = @original_Discontinued
Com sete parâmetros na coleção UpdateParameters.
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
runat="server" ...>
<DeleteParameters>
...
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="UnitPrice" Type="Decimal" />
<asp:Parameter Name="Discontinued" Type="Boolean" />
<asp:Parameter Name="original_ProductID" Type="Int32" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_UnitPrice" Type="Decimal" />
<asp:Parameter Name="original_Discontinued" Type="Boolean" />
</UpdateParameters>
...
</asp:SqlDataSource>
Da mesma forma, a DeleteCommand propriedade e a DeleteParameters coleção devem ser como o seguinte:
DELETE FROM [Products]
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
[UnitPrice] = @original_UnitPrice AND
[Discontinued] = @original_Discontinued
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
runat="server" ...>
<DeleteParameters>
<asp:Parameter Name="original_ProductID" Type="Int32" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_UnitPrice" Type="Decimal" />
<asp:Parameter Name="original_Discontinued" Type="Boolean" />
</DeleteParameters>
<UpdateParameters>
...
</UpdateParameters>
...
</asp:SqlDataSource>
Além de complementar as cláusulas WHERE e as propriedades UpdateCommand e DeleteCommand (e adicionar os parâmetros adicionais às respectivas coleções de parâmetros), selecionar a opção Usar concorrência otimista ajusta outras duas propriedades:
- Altera a
ConflictDetectionpropriedade deOverwriteChanges(o padrão) paraCompareAllValues - Altera a
OldValuesParameterFormatStringpropriedade de {0} (o padrão) para original_{0} .
Quando o controle da Web de dados invoca o método Update() ou Delete() do SqlDataSource, ele passa os valores originais. Se a propriedade SqlDataSource estiver configurada como ConflictDetection, esses valores originais serão adicionados ao comando. A OldValuesParameterFormatString propriedade fornece o padrão de nomenclatura usado para esses parâmetros de valor originais. O assistente Configurar Fonte de Dados usa original_{0} e nomeia cada parâmetro original nas propriedades UpdateCommand e DeleteCommand e nas coleções UpdateParameters e DeleteParameters adequadamente.
Observação
Como não estamos usando os recursos de inserção do controle SqlDataSource, fique à vontade para remover a InsertCommand propriedade e sua InsertParameters coleção.
ManipulandoNULLvalores corretamente
Infelizmente, as declarações ampliadas UPDATE e DELETE geradas automaticamente pelo assistente Configurar Fonte de Dados ao utilizar a simultaneidade otimista não funcionam com registros que contenham valores NULL. Para ver por que, considere nossos SqlDataSource s UpdateCommand:
UPDATE [Products] SET
[ProductName] = @ProductName,
[UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
[UnitPrice] = @original_UnitPrice AND
[Discontinued] = @original_Discontinued
A UnitPrice coluna na Products tabela pode ter NULL valores. Se um registro específico tiver um valor NULL para UnitPrice, a parte da cláusula WHERE[UnitPrice] = @original_UnitPrice será sempre avaliada como False porque retorna sempre False. Portanto, os registros que contêm NULL valores não podem ser editados ou excluídos, pois as instruções UPDATE e DELETE cláusulas WHERE não retornarão nenhuma linha para atualizar ou excluir.
Observação
Esse bug foi relatado pela primeira vez à Microsoft em junho de 2004 no SqlDataSource gera instruções SQL incorretas e supostamente está agendado para ser corrigido na próxima versão do ASP.NET.
Para corrigir isso, precisamos atualizar manualmente as cláusulas WHERE nas propriedades UpdateCommand e DeleteCommand para todas as colunas que podem ter valores NULL. Em geral, altere [ColumnName] = @original_ColumnName para:
(
([ColumnName] IS NULL AND @original_ColumnName IS NULL)
OR
([ColumnName] = @original_ColumnName)
)
Essa modificação pode ser feita diretamente por meio da marcação declarativa, por meio das opções UpdateQuery ou DeleteQuery da janela Propriedades ou por meio das guias UPDATE e DELETE na opção Especificar uma instrução SQL personalizada ou procedimento armazenado no assistente Configurar Fonte de Dados. Novamente, essa modificação deve ser feita para cada coluna na cláusula UpdateCommand e DeleteCommand s WHERE que pode conter NULL valores.
Aplicar isso ao nosso exemplo resulta nos seguintes valores modificados de UpdateCommand e DeleteCommand.
UPDATE [Products] SET
[ProductName] = @ProductName,
[UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
(([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
OR ([UnitPrice] = @original_UnitPrice)) AND
[Discontinued] = @original_Discontinued
DELETE FROM [Products]
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
(([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
OR ([UnitPrice] = @original_UnitPrice)) AND
[Discontinued] = @original_Discontinued
Etapa 2: Adicionar um GridView com Opções de Edição e Exclusão
Com o SqlDataSource configurado para dar suporte à simultaneidade otimista, tudo o que resta é adicionar um controle da Web de dados à página que utiliza esse controle de simultaneidade. Para este tutorial, vamos adicionar um GridView que fornece a funcionalidade de edição e exclusão. Para fazer isso, arraste um GridView da Caixa de Ferramentas para o Designer e defina-o ID como Products. Na tag inteligente do GridView, associe-o ao controle ProductsDataSourceWithOptimisticConcurrency SqlDataSource adicionado na Etapa 1. Por fim, verifique as opções Habilitar Edição e Habilitar Exclusão do tag inteligente.
Figura 6: Associar o GridView ao SqlDataSource e habilitar a edição e exclusão (clique para exibir a imagem em tamanho real)
Depois de adicionar o GridView, configure sua aparência removendo o ProductID BoundField, alterando a ProductName propriedade BoundField HeaderText para Product e atualizando o UnitPrice BoundField para que sua HeaderText propriedade seja simplesmente Price. O ideal é aprimorar a interface de edição para incluir um RequiredFieldValidator para o ProductName valor e um CompareValidator para o UnitPrice valor (para garantir que ele seja um valor numérico formatado corretamente). Consulte o tutorial Personalizando a Interface de Modificação de Dados para obter uma visão mais detalhada sobre como personalizar a interface de edição do GridView.
Observação
O estado de exibição do GridView deve ser habilitado, pois os valores originais passados do GridView para o SqlDataSource são armazenados no estado de exibição.
Depois de fazer essas modificações no GridView, a marcação declarativa GridView e SqlDataSource deve ser semelhante à seguinte:
<asp:SqlDataSource ID="ProductsDataSourceWithOptimisticConcurrency"
runat="server" ConflictDetection="CompareAllValues"
ConnectionString="<%$ ConnectionStrings:NORTHWNDConnectionString %>"
DeleteCommand=
"DELETE FROM [Products]
WHERE [ProductID] = @original_ProductID
AND [ProductName] = @original_ProductName
AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
OR ([UnitPrice] = @original_UnitPrice))
AND [Discontinued] = @original_Discontinued"
OldValuesParameterFormatString=
"original_{0}"
SelectCommand=
"SELECT [ProductID], [ProductName], [UnitPrice], [Discontinued]
FROM [Products]"
UpdateCommand=
"UPDATE [Products]
SET [ProductName] = @ProductName, [UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE [ProductID] = @original_ProductID
AND [ProductName] = @original_ProductName
AND (([UnitPrice] IS NULL AND @original_UnitPrice IS NULL)
OR ([UnitPrice] = @original_UnitPrice))
AND [Discontinued] = @original_Discontinued">
<DeleteParameters>
<asp:Parameter Name="original_ProductID" Type="Int32" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_UnitPrice" Type="Decimal" />
<asp:Parameter Name="original_Discontinued" Type="Boolean" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="UnitPrice" Type="Decimal" />
<asp:Parameter Name="Discontinued" Type="Boolean" />
<asp:Parameter Name="original_ProductID" Type="Int32" />
<asp:Parameter Name="original_ProductName" Type="String" />
<asp:Parameter Name="original_UnitPrice" Type="Decimal" />
<asp:Parameter Name="original_Discontinued" Type="Boolean" />
</UpdateParameters>
</asp:SqlDataSource>
<asp:GridView ID="Products" runat="server"
AutoGenerateColumns="False" DataKeyNames="ProductID"
DataSourceID="ProductsDataSourceWithOptimisticConcurrency">
<Columns>
<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
<asp:BoundField DataField="UnitPrice" HeaderText="Price"
SortExpression="UnitPrice" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
</Columns>
</asp:GridView>
Para ver o controle de simultaneidade otimista em ação, abra duas janelas do navegador e carregue a OptimisticConcurrency.aspx página em ambas. Clique nos botões Editar para o primeiro produto em ambos os navegadores. Em um navegador, altere o nome do produto e clique em Atualizar. O navegador fará o postback e o GridView retornará ao modo de pré-edição, mostrando o novo nome do produto para o registro recém-editado.
Na segunda janela do navegador, altere o preço (mas deixe o nome do produto como seu valor original) e clique em Atualizar. No postback, a grelha retorna ao seu modo anterior à edição, mas a alteração no preço não é registrada. O segundo navegador mostra o mesmo que o primeiro: o nome do novo produto com o preço antigo. As alterações feitas na segunda janela do navegador foram perdidas. Além disso, as alterações foram perdidas totalmente silenciosamente, pois não havia exceção ou mensagem indicando que uma violação de simultaneidade acabou de ocorrer.
Figura 7: As alterações na janela do segundo navegador foram silenciosamente perdidas (clique para exibir a imagem em tamanho real)
O motivo pelo qual as alterações do segundo navegador não foram confirmadas foi porque a UPDATE cláusula da WHERE instrução filtrava todos os registros e, portanto, não afetava nenhuma linha. Vamos examinar a UPDATE declaração novamente.
UPDATE [Products] SET
[ProductName] = @ProductName,
[UnitPrice] = @UnitPrice,
[Discontinued] = @Discontinued
WHERE
[ProductID] = @original_ProductID AND
[ProductName] = @original_ProductName AND
(([UnitPrice] IS NULL AND @original_UnitPrice IS NULL) OR
([UnitPrice] = @original_UnitPrice)) AND
[Discontinued] = @original_Discontinued
Quando a segunda janela do navegador atualiza o registro, o nome do produto original especificado na WHERE cláusula não corresponde ao nome do produto existente (já que ele foi alterado pelo primeiro navegador). Portanto, a instrução [ProductName] = @original_ProductName retorna False e UPDATE não afeta nenhum registro.
Observação
A exclusão funciona da mesma maneira. Com duas janelas do navegador abertas, comece editando um determinado produto com um e salvando suas alterações. Depois de salvar as alterações em um navegador, clique no botão Excluir para o mesmo produto no outro. Como os valores originais não correspondem à DELETE cláusula da WHERE instrução, a exclusão falha silenciosamente.
Da perspectiva do usuário final na segunda janela do navegador, depois de clicar no botão Atualizar, a grade retorna ao modo anterior à edição, mas as alterações feitas foram perdidas. No entanto, não há indicação visual de que suas alterações não se mantiveram. O ideal seria que, se as alterações de um usuário fossem perdidas devido a uma violação de concorrência, nós o notificássemos e, talvez, mantivéssemos a grade no modo de edição. Vamos ver como fazer isso.
Etapa 3: Determinar quando ocorreu uma violação de concorrência
Como uma violação de simultaneidade rejeita as alterações feitas, seria bom alertar o usuário quando ocorreu uma violação de simultaneidade. Para alertar o usuário, vamos adicionar um controle Web label à parte superior da página chamada ConcurrencyViolationMessage cuja Text propriedade exibe a seguinte mensagem: Você tentou atualizar ou excluir um registro que foi atualizado simultaneamente por outro usuário. Examine as alterações do outro usuário e, em seguida, refaça sua atualização ou exclusão. Defina a propriedade do controle Label no CssClass como Warning, que é uma classe CSS definida em Styles.css e que exibe o texto em uma fonte vermelha, itálica, negrito e grande. Por fim, defina as propriedades dos Rótulos Visible e EnableViewState para False. Isso ocultará o Rótulo, exceto apenas para os postbacks em que definimos explicitamente sua Visible propriedade como True.
Figura 8: Adicionar um controle de rótulo à página para exibir o aviso (clique para exibir a imagem em tamanho real)
Ao executar uma atualização ou exclusão, os manipuladores de RowUpdated eventos e RowDeleted GridView são acionados após o controle da fonte de dados executar a atualização ou exclusão solicitada. Podemos determinar quantas linhas foram afetadas pela operação desses manipuladores de eventos. Se nenhuma linha for afetada, queremos exibir o Rótulo ConcurrencyViolationMessage.
Crie um manipulador de eventos para os eventos RowUpdated e RowDeleted e adicione o seguinte código:
Protected Sub Products_RowUpdated(sender As Object, e As GridViewUpdatedEventArgs) _
Handles Products.RowUpdated
If e.AffectedRows = 0 Then
ConcurrencyViolationMessage.Visible = True
e.KeepInEditMode = True
' Rebind the data to the GridView to show the latest changes
Products.DataBind()
End If
End Sub
Protected Sub Products_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _
Handles Products.RowDeleted
If e.AffectedRows = 0 Then
ConcurrencyViolationMessage.Visible = True
End If
End Sub
Em ambos os manipuladores de eventos, verificamos a propriedade e.AffectedRows e, se for igual a 0, definimos a propriedade ConcurrencyViolationMessage do rótulo Visible como True.
RowUpdated No manipulador de eventos, também instruimos o GridView a permanecer no modo de edição definindo a KeepInEditMode propriedade como true. Ao fazer isso, precisamos vincular novamente os dados à grade para que os dados do outro usuário sejam carregados na interface de edição. Isso é feito chamando o método GridView DataBind() .
Como mostra a Figura 9, com esses dois manipuladores de eventos, uma mensagem muito perceptível é exibida sempre que ocorre uma violação de simultaneidade.
Figura 9: Uma mensagem é exibida em face de uma violação de simultaneidade (clique para exibir a imagem em tamanho real)
Resumo
Ao criar um aplicativo Web em que vários usuários simultâneos podem estar editando os mesmos dados, é importante considerar as opções de controle de simultaneidade. Por padrão, os controles da Web de dados ASP.NET e controles de fonte de dados não empregam nenhum controle de simultaneidade. Como vimos neste tutorial, implementar o controle de simultaneidade otimista com o SqlDataSource é relativamente rápido e fácil. O SqlDataSource realiza a maior parte do trabalho para adicionar cláusulas aumentadas WHERE às instruções geradas automaticamente UPDATE e DELETE, mas há algumas sutilezas no tratamento de colunas de valor NULL, conforme discutido na seção Tratamento Correto de Valores NULL.
Este tutorial conclui nosso exame do SqlDataSource. Nossos tutoriais restantes voltarão a trabalhar com dados usando o ObjectDataSource e a arquitetura em camadas.
Divirta-se programando!
Sobre o autor
Scott Mitchell, autor de sete livros 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 alcançado em mitchell@4GuysFromRolla.com.