Passo a passo: Estendendo o Cache de Banco de Dados Local para dar suporte à sincronização bidirecional
No Visual Studio 2008, o Cache de Banco de Dados Local configura um banco de dados do SQL Server Compact e um conjunto de classes parciais que habilitam Sync Framework. Como o Visual Studio gera classes parciais, você pode escrever código para adicionar a funcionalidade de sincronização e ainda manter a capacidade de exibir e alterar configurações na caixa de diálogo Configurar Sincronização de Dados. Para obter mais informações sobre o Cache de Banco de Dados Local e classes parciais, consulte a documentação do Visual Studio 2008.
Por padrão, a caixa de diálogo Configurar Sincronização de Dados permite configurar o Sync Framework somente para cenários de download. Isso significa que, depois que você configurar a sincronização de dados, chamar Synchronize apenas fará com que alterações do servidor sejam baixadas no banco de dados do cliente. Uma das formas mais comuns de se estender o código de sincronização é configurar a sincronização bidirecional. Desse modo, você pode carregar alterações do cliente para o servidor. Para habilitar a sincronização bidirecional, é recomendável estender o código gerado das seguintes maneiras:
Defina a direção da sincronização como bidirecional.
Adicione código para resolver conflitos de sincronização.
Remova colunas de controle de servidor dos comandos de sincronização.
Dica
O Visual Studio 2008 usa o Sync Framework for ADO.NET 1.0 ao gerar código para o Cache de Banco de Dados Local.
Pré-requisitos
Antes de começar este passo a passo, você deve concluir o "Passo a passo: Criando um aplicativo ocasionalmente conectado" na documentação do Visual Studio 2008. Depois de concluir esse passo a passo, você terá um projeto que contém um Cache de Banco de Dados Local e um aplicativo do Windows Form que permite baixar alterações da tabela Customer do Northwind em um banco de dados SQL Server Compact. Agora você está pronto para carregar esse passo a passo e adicionar a funcionalidade bidirecional.
Para abrir a solução OCSWalkthrough
Abra o Visual Studio.
No menu Arquivo, abra uma solução ou um projeto existente e localize a solução OCSWalkthrough. É o arquivo OCSWalkthrough.sln.
Definindo a direção da sincronização
A caixa de diálogo Configurar Sincronização de Dados define a propriedade SyncDirection como DownloadOnly ou Snapshot. Para habilitar a sincronização bidirecional, defina a propriedade SyncDirection como Bidirectional para cada tabela que você deseja habilitar para carregamento de alterações.
Para definir a direção da sincronização
Clique com o botão direito do mouse em NorthwindCache.sync e selecione Exibir Código. Quando você faz isso pela primeira vez, o Visual Studio cria um arquivo de classe NorthwindCache no nó NorthwindCache.sync do Gerenciador de Soluções. Esse arquivo contém uma classe parcial
NorthwindCacheSyncAgent
, e você pode adicionar outras classes conforme necessário.No arquivo de classe NorthwindCache, adicione uma linha de código ao método
NorthwindCacheSyncAgent.OnInitialized()
:public partial class NorthwindCacheSyncAgent { partial void OnInitialized() { this.Customers.SyncDirection = Microsoft.Synchronization.Data.SyncDirection.Bidirectional; } }
Partial Public Class NorthwindCacheSyncAgent Partial Sub OnInitialized() Me.Customers.SyncDirection = Microsoft.Synchronization.Data.SyncDirection.Bidirectional End Sub End Class
Abra Form1 no Editor de Códigos.
No arquivo Form1, edite a linha de código no manipulador de eventos
SynchronizeButton_Click
para que inclua estatísticas de carregamento e download:MessageBox.Show("Changes downloaded: " + syncStats.TotalChangesDownloaded.ToString() + Environment.NewLine + "Changes uploaded: " + syncStats.TotalChangesUploaded.ToString());
MessageBox.Show("Changes downloaded: " & _ syncStats.TotalChangesDownloaded.ToString & Environment.NewLine & "Changes uploaded: " & _ syncStats.TotalChangesUploaded.ToString)
Para sincronizar e exibir estatísticas
Pressione F5.
No formulário, atualize um registro e clique no botão Salvar da barra de ferramentas.
Clique em Sincronizar Agora.
É exibida uma caixa de mensagem com informações sobre os registros sincronizados. As estatísticas mostram que uma linha foi carregada e que outra foi baixada, embora nenhuma alteração tenha sido feita no servidor. O download adicional ocorre porque alterações do cliente são retornadas para ele depois de aplicadas no servidor. Para obter mais informações, consulte "Determinando qual cliente fez uma alteração de dados" em Como usar um sistema de controle de alterações personalizado.
Clique em OK para fechar a caixa de mensagem, mas deixar o aplicativo em execução.
Para sincronizar e exibir resolução de conflitos
No formulário, atualize um registro e clique no botão Salvar.
Com o aplicativo ainda em execução, use Gerenciador de Servidores/Gerenciador de Banco de Dados (ou outra ferramenta de gerenciamento de banco de dados) para se conectar ao banco de dados do servidor.
Para demonstrar o comportamento padrão de resolução de conflitos, no Gerenciador de Servidores/Gerenciador de Banco de Dados, atualize o mesmo registro que você atualizou no formulário, mas com um valor diferente, e confirme a alteração. (Saia da linha modificada.)
Volte ao formulário e clique em Sincronizar Agora.
Verifique a atualização na grade do aplicativo e no banco de dados do servidor. Observe que a atualização feita no servidor substituiu a atualização feita no cliente. Para obter informações sobre como alterar esse comportamento de resolução de conflitos, consulte a próxima seção deste tópico, "Adicionar código para resolver conflitos de sincronização".
Adicionar código para resolver conflitos de sincronização
No Sync Framework, dizemos que uma linha está em conflito quando ela foi alterada no cliente e no servidor entre as sincronizações. O Sync Framework dispõe de um conjunto de recursos que você pode usar para detectar e resolver conflitos. Neste passo a passo, você adicionará resolução básica de conflitos em que a mesma linha foi atualizada no cliente e no servidor. Outros tipos de conflitos incluem uma linha excluída de um banco de dados e atualizada em outro ou linhas que têm chaves primárias duplicadas inseridas nos dois bancos de dados. Para obter mais informações sobre como detectar e resolver conflitos, consulte Como tratar conflitos de dados e erros.
Para adicionar resolução de conflitos
Adicione código para resolver o evento ApplyChangeFailed do servidor e o evento ApplyChangeFailed do cliente. Esses eventos são disparados quando não é possível aplicar uma linha devido a um conflito ou erro. Os métodos que manipulam esses eventos no código de exemplo verificam o tipo de conflito e especificam se os conflitos de atualização no cliente/servidor devem ser resolvidos forçando a gravação da alteração do cliente no banco de dados do servidor. O comando de sincronização que aplica atualizações no banco de dados do servidor inclui lógica para reconhecer quando uma alteração deve ser forçada. Esse comando é incluído no código da próxima seção deste tópico, "Removendo colunas de controle de servidor dos comandos de sincronização".
Dica
O código de exemplo mostra um exemplo básico de resolução de conflitos. A forma como você resolve conflitos depende dos requisitos do aplicativo e da lógica de negócios.
A maneira como você adiciona código para C# é diferente de Visual Basic:
Para C#, adicione código a NorthwindCache.cs e Form1.cs. Em NorthwindCache.cs, adicione o seguinte código após o final da classe
NorthwindCacheSyncAgent
:public partial class NorthwindCacheServerSyncProvider { partial void OnInitialized() { this.ApplyChangeFailed += new System.EventHandler<Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs> (NorthwindCacheServerSyncProvider_ApplyChangeFailed); } private void NorthwindCacheServerSyncProvider_ApplyChangeFailed(object sender, Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs e) { if (e.Conflict.ConflictType == Microsoft.Synchronization.Data.ConflictType.ClientUpdateServerUpdate) { //Resolve a client update / server update conflict by force writing //the client change to the server database. System.Windows.Forms.MessageBox.Show("A client update / server update conflict " + "was detected at the server."); e.Action = Microsoft.Synchronization.Data.ApplyAction.RetryWithForceWrite; } } } public partial class NorthwindCacheClientSyncProvider { public void AddHandlers() { this.ApplyChangeFailed += new System.EventHandler<Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs> (NorthwindCacheClientSyncProvider_ApplyChangeFailed); } private void NorthwindCacheClientSyncProvider_ApplyChangeFailed(object sender, Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs e) { if (e.Conflict.ConflictType == Microsoft.Synchronization.Data.ConflictType.ClientUpdateServerUpdate) { //Resolve a client update / server update conflict by keeping the //client change. e.Action = Microsoft.Synchronization.Data.ApplyAction.Continue; } } }
Em Form1.cs, edite o código no manipulador de eventos
SynchronizeButton_Click
para que chame o métodoAddHandlers
adicionado a NorthwindCache.cs na etapa anterior:NorthwindCacheSyncAgent syncAgent = new NorthwindCacheSyncAgent(); NorthwindCacheClientSyncProvider clientSyncProvider = (NorthwindCacheClientSyncProvider)syncAgent.LocalProvider; clientSyncProvider.AddHandlers(); Microsoft.Synchronization.Data.SyncStatistics syncStats = syncAgent.Synchronize();
Para Visual Basic, em NorthwindCache.vb adicione o seguinte código após a instrução
End Class
da classeNorthwindCacheSyncAgent
.Partial Public Class NorthwindCacheServerSyncProvider Private Sub NorthwindCacheServerSyncProvider_ApplyChangeFailed( _ ByVal sender As Object, ByVal e As _ Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs) _ Handles Me.ApplyChangeFailed If e.Conflict.ConflictType = _ Microsoft.Synchronization.Data.ConflictType.ClientUpdateServerUpdate Then 'Resolve a client update / server update conflict by force writing 'the client change to the server database. MessageBox.Show("A client update / server update conflict was detected at the server.") e.Action = Microsoft.Synchronization.Data.ApplyAction.RetryWithForceWrite End If End Sub End Class Partial Public Class NorthwindCacheClientSyncProvider Private Sub NorthwindCacheClientSyncProvider_ApplyChangeFailed( _ ByVal sender As Object, ByVal e As _ Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs) _ Handles Me.ApplyChangeFailed If e.Conflict.ConflictType = _ Microsoft.Synchronization.Data.ConflictType.ClientUpdateServerUpdate Then 'Resolve a client update / server update conflict by keeping the 'client change. e.Action = Microsoft.Synchronization.Data.ApplyAction.Continue End If End Sub End Class
Para sincronizar e exibir resolução de conflitos
Pressione F5.
No formulário, atualize um registro e clique no botão Salvar.
No Gerenciador de Servidores/Gerenciador de Banco de Dados, atualize o mesmo registro que você atualizou no formulário, mas com um valor diferente, e confirme a alteração.
Volte ao formulário e clique em Sincronizar Agora.
Verifique a atualização na grade do aplicativo e no banco de dados do servidor. Observe que a atualização feita no cliente substituiu a atualização feita no servidor.
Removendo colunas de controle de servidor dos comandos de sincronização
Quando o Cache de Banco de Dados Local é criado, as colunas usadas para controlar alterações no banco de dados do servidor são baixadas no cliente. (Neste passo a passo, as colunas são CreationDate
e LastEditDate
.) Para dar suporte à sincronização bidirecional e ajudar a garantir a convergência de dados no cliente e no servidor, remova essas colunas dos comandos SQL que aplicam alterações no banco de dados do servidor. Também é possível remover as colunas dos comandos que selecionam alterações do servidor para aplicá-las ao cliente, mas isso não é necessário. Devido a restrições em algumas alterações de esquema no banco de dados do cliente, as colunas não podem ser descartadas. Para obter mais informações sobre comandos de sincronização, consulte Como especificar sincronização de instantâneo, de download, de carregamento e bidirecional.
Dica
Se você usa o controle de alterações do SQL Server, as colunas de controle não são adicionadas às suas tabelas. Nesse caso, você não precisa alterar os comandos que aplicam alterações no servidor.
Para remover colunas de controle dos comandos de sincronização
Adicione o seguinte código à classe
NorthwindCache
(NorthwindCache.vb ou NorthwindCache.cs) após a instruçãoEnd Class
da classeNorthwindCacheServerSyncProvider
. Esse código redefine dois comandos que são definidos como propriedades no objeto SyncAdapter para a tabelaCustomers
: as propriedades InsertCommand e UpdateCommand. Os comandos gerados pela caixa de diálogo Configurar Sincronização de Dados continham referências às colunasCreationDate
eLastEditDate
. Esses comandos foram redefinidos no métodoOnInitialized
da classeCustomersSyncAdapter
. A propriedade DeleteCommand não foi redefinida porque não afeta as colunasCreationDate
ouLastEditDate
.As variáveis de cada comando SQL são usadas para passar dados e metadados entre o Sync Framework, o cliente e o servidor. As seguintes variáveis de sessão são usadas nos comandos abaixo:
@sync_row_count
: retorna o número de linhas afetadas pela última operação no servidor. Nos bancos de dados SQL Server, @@ROWCOUNT fornece o valor para essa variável.@sync_force_write
: usada para forçar a aplicação de uma alteração que falhou devido a um conflito ou erro.@sync_last_received_anchor
: usada para definir o conjunto de alterações a ser sincronizado durante uma sessão.
Para obter mais informações sobre variáveis de sessão, consulte Como usar variáveis de sessão.
public partial class CustomersSyncAdapter { partial void OnInitialized() { //Redefine the insert command so that it does not insert values //into the CreationDate and LastEditDate columns. System.Data.SqlClient.SqlCommand insertCommand = new System.Data.SqlClient.SqlCommand(); insertCommand.CommandText = "INSERT INTO dbo.Customers ([CustomerID], [CompanyName], " + "[ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], " + "[Country], [Phone], [Fax] )" + "VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, " + "@Region, @PostalCode, @Country, @Phone, @Fax) SET @sync_row_count = @@rowcount"; insertCommand.CommandType = System.Data.CommandType.Text; insertCommand.Parameters.Add("@CustomerID", System.Data.SqlDbType.NChar); insertCommand.Parameters.Add("@CompanyName", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@ContactName", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@ContactTitle", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Address", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@City", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Region", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@PostalCode", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Country", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Phone", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Fax", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@sync_row_count", System.Data.SqlDbType.Int); insertCommand.Parameters["@sync_row_count"].Direction = System.Data.ParameterDirection.Output; this.InsertCommand = insertCommand; //Redefine the update command so that it does not update values //in the CreationDate and LastEditDate columns. System.Data.SqlClient.SqlCommand updateCommand = new System.Data.SqlClient.SqlCommand(); updateCommand.CommandText = "UPDATE dbo.Customers SET [CompanyName] = @CompanyName, [ContactName] " + "= @ContactName, [ContactTitle] = @ContactTitle, [Address] = @Address, [City] " + "= @City, [Region] = @Region, [PostalCode] = @PostalCode, [Country] = @Country, " + "[Phone] = @Phone, [Fax] = @Fax " + "WHERE ([CustomerID] = @CustomerID) AND (@sync_force_write = 1 " + "OR ([LastEditDate] <= @sync_last_received_anchor)) SET @sync_row_count = @@rowcount"; updateCommand.CommandType = System.Data.CommandType.Text; updateCommand.Parameters.Add("@CompanyName", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@ContactName", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@ContactTitle", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Address", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@City", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Region", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@PostalCode", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Country", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Phone", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Fax", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@CustomerID", System.Data.SqlDbType.NChar); updateCommand.Parameters.Add("@sync_force_write", System.Data.SqlDbType.Bit); updateCommand.Parameters.Add("@sync_last_received_anchor", System.Data.SqlDbType.DateTime); updateCommand.Parameters.Add("@sync_row_count", System.Data.SqlDbType.Int); updateCommand.Parameters["@sync_row_count"].Direction = System.Data.ParameterDirection.Output; this.UpdateCommand = updateCommand; } }
Partial Public Class CustomersSyncAdapter Private Sub OnInitialized() 'Redefine the insert command so that it does not insert values 'into the CreationDate and LastEditDate columns. Dim insertCommand As New System.Data.SqlClient.SqlCommand With insertCommand .CommandText = "INSERT INTO dbo.Customers ([CustomerID], [CompanyName], " & _ "[ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], " & _ "[Country], [Phone], [Fax] )" & _ "VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, " & _ "@Region, @PostalCode, @Country, @Phone, @Fax) SET @sync_row_count = @@rowcount" .CommandType = System.Data.CommandType.Text .Parameters.Add("@CustomerID", System.Data.SqlDbType.NChar) .Parameters.Add("@CompanyName", System.Data.SqlDbType.NVarChar) .Parameters.Add("@ContactName", System.Data.SqlDbType.NVarChar) .Parameters.Add("@ContactTitle", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Address", System.Data.SqlDbType.NVarChar) .Parameters.Add("@City", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Region", System.Data.SqlDbType.NVarChar) .Parameters.Add("@PostalCode", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Country", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Phone", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Fax", System.Data.SqlDbType.NVarChar) .Parameters.Add("@sync_row_count", System.Data.SqlDbType.Int) .Parameters("@sync_row_count").Direction = ParameterDirection.Output End With Me.InsertCommand = insertCommand 'Redefine the update command so that it does not update values 'in the CreationDate and LastEditDate columns. Dim updateCommand As New System.Data.SqlClient.SqlCommand With updateCommand .CommandText = "UPDATE dbo.Customers SET [CompanyName] = @CompanyName, [ContactName] " & _ "= @ContactName, [ContactTitle] = @ContactTitle, [Address] = @Address, [City] " & _ "= @City, [Region] = @Region, [PostalCode] = @PostalCode, [Country] = @Country, " & _ "[Phone] = @Phone, [Fax] = @Fax " & _ "WHERE ([CustomerID] = @CustomerID) AND (@sync_force_write = 1 " & _ "OR ([LastEditDate] <= @sync_last_received_anchor)) SET @sync_row_count = @@rowcount" .CommandType = System.Data.CommandType.Text .Parameters.Add("@CompanyName", System.Data.SqlDbType.NVarChar) .Parameters.Add("@ContactName", System.Data.SqlDbType.NVarChar) .Parameters.Add("@ContactTitle", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Address", System.Data.SqlDbType.NVarChar) .Parameters.Add("@City", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Region", System.Data.SqlDbType.NVarChar) .Parameters.Add("@PostalCode", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Country", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Phone", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Fax", System.Data.SqlDbType.NVarChar) .Parameters.Add("@CustomerID", System.Data.SqlDbType.NChar) .Parameters.Add("@sync_force_write", System.Data.SqlDbType.Bit) .Parameters.Add("@sync_last_received_anchor", System.Data.SqlDbType.DateTime) .Parameters.Add("@sync_row_count", System.Data.SqlDbType.Int) .Parameters("@sync_row_count").Direction = ParameterDirection.Output End With Me.UpdateCommand = updateCommand End Sub End Class
Para sincronizar e exibir uma atualização de coluna de controle
Pressione F5.
No formulário, atualize um registro alterando um valor da coluna
LastEditDate
e clique no botão Salvar.Volte ao formulário e clique em Sincronizar Agora.
Verifique a atualização na grade do aplicativo e no banco de dados do servidor. Observe que o valor da coluna do servidor substituiu a atualização no cliente. O processo de atualização consiste no seguinte:
O Sync Framework identifica que uma linha foi alterada no cliente.
Durante a sincronização, a linha é carregada e aplicada à tabela do banco de dados do servidor. No entanto, as colunas de controle não são incluídas na instrução de atualização. O Sync Framework efetivamente executa uma "atualização fictícia" na tabela.
Agora a linha é retornada para o cliente, mas os comandos que selecionam alterações do servidor incluem as colunas de controle. Dessa forma, a alteração feita no cliente é substituída pelo valor do servidor.
Conclusão
Neste passo a passo, você configurou a sincronização bidirecional com resolução básica de conflitos e resolveu o possível problema de ter colunas de controle de servidor no banco de dados do cliente. Utilizando classes parciais, é possível estender o código do Cache de Banco de Dados Local de outras maneiras pertinentes. Você pode, por exemplo, redefinir os comandos SQL que selecionam alterações do banco de dados do servidor para que os dados sejam filtrados quando baixados no cliente. Recomendamos que você leia os tópicos de instruções desta documentação para entender as formas pelas quais é possível adicionar ou alterar o código de sincronização para atender aos requisitos dos seus aplicativos. Para obter mais informações, consulte Programando tarefas comuns de sincronização do cliente e do servidor.
Consulte também
Conceitos
Programando tarefas comuns de sincronização do cliente e do servidor
Ferramentas para ajudar no desenvolvimento de aplicativos