Como usar um sistema de controle de alterações personalizado
Muitos aplicativos exigem que as alterações sejam controladas no banco de dados do servidor para que possam ser entregues a clientes durante uma sessão de sincronização subsequente. Este tópico descreve os requisitos de um sistema de controle de alterações e mostra como criar um sistema personalizado que o Sync Framework pode usar. O controle de alterações personalizado é apropriado em alguns casos. No entanto, você deve estar ciente de que ele introduz complexidade e pode afetar o desempenho do banco de dados do servidor. Se você estiver usando o SQL Server 2008, é recomendável utilizar o recurso de controle de alterações do SQL Server. Para obter mais informações, consulte Como usar o controle de alterações do SQL Server.
Requisitos do servidor para cenários de sincronização
O Sync Framework foi projetado para ter efeito mínimo sobre o banco de dados do servidor. Por isso, as modificações necessárias para o controle de alterações no banco de dados do servidor são proporcionais ao nível de funcionalidade desejado em um aplicativo. Tenha em mente as seguintes considerações:
Em uma extremidade do espectro, há um instantâneo de dados somente para download. Isso não requer alterações.
Na outra, existe a sincronização bidirecional que usa controle total de alterações e detecção de conflitos.
A tabela a seguir resume as maneiras como o Sync Framework pode ser usado, e identifica as exigências correspondentes para o banco de dados do servidor.
Cenário | Chave primária ou coluna exclusiva1 | Controlar tempo de atualização | Controlar tempo de inserção | Controlar tempo de exclusão | Controlar ID do cliente para atualizações | Controlar ID do cliente para inserções | Controlar ID do cliente para exclusões |
---|---|---|---|---|---|---|---|
Baixar um instantâneo de dados no cliente. |
Não |
Não |
Não |
Não |
Não |
Não |
Não |
Baixar inserções e atualizações incrementais no cliente. |
Sim |
Sim |
Sim2 |
Não |
Não |
Não |
Não |
Baixar inserções, atualizações e exclusões incrementais no cliente. |
Sim |
Sim |
Sim2 |
Sim |
Não |
Não |
Não |
Carregar inserções no servidor. |
Sim |
Não |
Não |
Não |
Não |
Não3 |
Não |
Carregar inserções e atualizações no servidor. |
Sim |
Não |
Não |
Não |
Não3 |
Não3 |
Não |
Carregar inserções, atualizações e exclusões no servidor. |
Sim |
Não |
Não |
Não |
Não3 |
Não3 |
Não3 |
Inserções e atualizações bidirecionais com detecção de conflito. |
Sim |
Sim |
Sim2 |
Não |
Sim4 |
Sim4 |
Não |
Inserções, atualizações e exclusões bidirecionais com detecção de conflito. |
Sim |
Sim |
Sim2 |
Sim |
Sim4 |
Sim4 |
Sim4 |
1 As chaves primárias devem ser exclusivas em todos os nós e não devem ser reutilizadas. Se uma linha for excluída, a chave primária dessa linha não deverá ser usada para outra linha. As colunas de identidade não costumam ser uma escolha apropriada para ambientes distribuídos. Para obter mais informações sobre chaves primárias, consulte Selecionando uma chave primária adequada para um ambiente distribuído.
2 Obrigatório se você desejar distinguir entre inserções e atualizações. Para obter mais informações, consulte "Determinando quais alterações de dados devem ser baixadas em um cliente" posteriormente neste tópico.
3 Obrigatório se mais de um cliente tiver que alterar uma linha e você desejar identificar qual cliente fez a alteração. Para obter mais informações, consulte "Identificando qual cliente fez uma alteração de dados" neste tópico.
4 Obrigatório se você não desejar repetir as alterações novamente no cliente que as fez. Para obter mais informações, consulte "Identificando qual cliente fez uma alteração de dados" neste tópico.
Dica
Além das alterações descritas anteriormente, você provavelmente criará procedimentos armazenados para acesso a dados. A maioria dos exemplos nesta documentação usa SQL embutido porque é mais fácil para mostrar o que está ocorrendo no código. Em aplicativos de produção, os procedimentos armazenados devem ser usados pelos seguintes motivos: eles encapsulam o código, geralmente têm um melhor desempenho e podem fornecer maior segurança em relação ao SQL embutido se forem escritos corretamente.
Determinando quais alterações de dados devem ser baixadas em um cliente
Nas sincronizações somente para download e bidirecional, você deve controlar as alterações no servidor para que o Sync Framework possa determinar quais alterações devem ser baixadas nos clientes. Embora o Sync Framework não defina especificamente como oferecer suporte ao controle de alterações, há uma maneira comum de abordá-lo. Para cada tabela a ser sincronizada, você pode usar a seguinte abordagem:
Adicione uma coluna que controla quando uma linha foi inserida no banco de dados do servidor.
Adicione uma coluna e, em alguns casos, um disparador que controla quando uma linha foi atualizada pela última vez no banco de dados do servidor.
Adicione uma tabela de marcas de exclusão e um disparador que controla quando uma linha foi excluída do banco de dados do servidor. Se você não quiser excluir dados do servidor, mas precisar enviar exclusões ao cliente, as exclusões lógicas poderão ser controladas na tabela base: use uma coluna, geralmente do tipo bit, para indicar que uma linha foi excluída e outra coluna para controlar quando a exclusão ocorreu.
Essas colunas e as tabelas de marca de exclusão são usadas junto com as âncoras para determinar quais inserções, atualizações e exclusões devem ser baixadas. Uma âncora é apenas um ponto no tempo que é usado para definir um conjunto de alterações a serem sincronizadas. Considere as consultas a seguir:
A consulta que você especifica para a propriedade SelectIncrementalInsertsCommand. Essa consulta baixa inserções incrementais da tabela
Sales.Customer
no banco de dados de exemplo Sync Framework, da seguinte maneira:SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor
Para obter mais informações sobre essa propriedade e sobre outras relacionadas aos comandos de sincronização, consulte Como especificar sincronização de instantâneo, de download, de carregamento e bidirecional.
A consulta que você especifica para a propriedade SelectNewAnchorCommand. Essa consulta recupera um valor de ponto no tempo. A coluna
InsertTimestamp
armazena valores de carimbo de data/hora. Portanto a consulta usa a função Transact-SQLMIN_ACTIVE_ROWVERSION
, introduzida no SQL Server 2005 Service Pack 2, para recuperar um valor de carimbo de data/hora do banco de dados do servidor, da seguinte maneira:SELECT @sync_new_received_anchor = MIN_ACTIVE_ROWVERSION - 1
MIN_ACTIVE_ROWVERSION retorna o valor ativo mais baixo timestamp (também conhecido como rowversion) no banco de dados atual. Um valor timestamp estará ativo se for usado em uma transação que ainda não foi confirmada. Se não houver valores ativos no banco de dados, MIN_ACTIVE_ROWVERSION retornará o mesmo valor que @@DBTS + 1. MIN_ACTIVE_ROWVERSION é útil em cenários como sincronização de dados que usam valores de timestamp para agrupar conjuntos de alterações. Se um aplicativo usar @@DBTS em seus comandos de âncora, em vez de MIN_ACTIVE_ROWVERSION, é possível que as alterações que estão ativas quando a sincronização ocorre sejam perdidas.
Quando a tabela Sales.Customer
for sincronizada pela primeira vez, ocorrerá o processo a seguir:
O novo comando de âncora é executado. O comando retorna o valor
0x0000000000000D49
. Esse valor é armazenado no banco de dados do cliente. A tabela nunca foi sincronizada. Portanto não há nenhum valor de âncora armazenado no banco de dados do cliente a partir de uma sincronização anterior. Neste caso, o Sync Framework usa o menor valor disponível para o tipo de dados SQL Server timestamp:0x0000000000000000
. A consulta executada pelo Sync Framework é a seguinte. Esta consulta baixa o esquema e todas as linhas da tabela.exec sp_executesql N'SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE (InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor)',N'@sync_last_received_anchor timestamp, @sync_new_received_anchor timestamp', @sync_last_received_anchor=0x0000000000000000, @sync_new_received_anchor=0x0000000000000D49
Durante a segunda sincronização, o novo comando de âncora é executado. Linhas foram inseridas desde a última sincronização. Portanto, o comando retornará o valor
0x0000000000000D4C
. A tabela foi sincronizada anteriormente. Portanto, o Sync Framework pode recuperar o valor de âncora0x0000000000000D49
. Esse valor é armazenado no banco de dados cliente a partir da sincronização anterior. A consulta executada é: A consulta baixa apenas aquelas linhas da tabela que foram inseridas entre os dois valores de âncora.exec sp_executesql N'SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE (InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor)', N'@sync_last_received_anchor timestamp, @sync_new_received_anchor timestamp', @sync_last_received_anchor=0x0000000000000D49, @sync_new_received_anchor=0x0000000000000D4C
Para obter exemplos de comandos de atualização e exclusão, consulte Como baixar alterações de dados incrementais em um cliente e Como trocar alterações de dados incrementais bidirecionais entre um cliente e um servidor.
Conforme observado anteriormente, o comando usado para recuperar os valores de âncora depende do tipo de dados das colunas de controle no banco de dados do servidor. Os exemplos desta documentação usam SQL Server timestamp, também conhecido como rowversion. Para usar uma coluna SQL Server datetime, a consulta do novo comando de âncora é semelhante a:
SELECT @sync_new_received_anchor = GETUTCDATE()
Para determinar qual tipo de dados deve ser usado para uma âncora, avalie os requisitos do aplicativo e considere quanta flexibilidade você tem para alterar o esquema do banco de dados do servidor. Se o banco de dados estiver em desenvolvimento, você poderá especificar exatamente quais colunas e gatilhos devem ser adicionados. Se o banco de dados estiver em produção, suas opções poderão ser mais limitadas. Considere as orientações a seguir:
Todas as tabelas em um grupo de sincronização devem usar o mesmo tipo de dados e o novo comando de âncora. Se possível, use o mesmo tipo de dados e comando para todos os grupos.
O tipo de dados datetime é fácil de entender, e as tabelas geralmente já têm uma coluna que controla quando uma linha foi modificada. No entanto esse tipo de dados poderá ser problemático se os clientes estiverem em fusos horários diferentes. Se você usar esse tipo de dados, as transações poderão ser perdidas quando alterações incrementais forem selecionadas.
O tipo de dados timestamp é preciso e não depende da hora do relógio. No entanto, cada tabela em um banco de dados do SQL Server pode conter apenas uma coluna desse tipo de dados. Portanto, se você tiver que distinguir entre inserções e atualizações, poderá adicionar uma coluna de um tipo de dados diferente, como binary(8), e armazenar os valores de carimbo de data/hora naquela coluna. Para obter um exemplo, consulte Scripts de instalação para tópicos de instruções do provedor de banco de dados. O tipo de dados timestamp poderá ser um problema, se o banco de dados do servidor for restaurado de um backup. Para obter mais informações, consulte Objetos do banco de dados com suporte do Sync Framework. Conforme observado anteriormente, é recomendável usar MIN_ACTIVE_ROWVERSION no comando que seleciona uma nova âncora.
Identificando qual cliente fez uma alteração de dados
Há dois motivos principais para identificar qual cliente fez uma alteração de dados:
Para oferecer suporte à detecção e resolução de conflitos em sincronizações bidirecionais e somente para carregamento.
Se o servidor e o cliente ou mais de um cliente puderem alterar uma determinada linha, talvez você queira identificar quem fez a alteração. Essas informações permitem que você escreva código, por exemplo, que priorize uma alteração sobre outra. Sem essas informações, a última alteração feita na linha é mantida.
Para impedir a repetição de alterações no cliente durante a sincronização bidirecional.
O Sync Framework primeiro carrega as alterações no servidor e, em seguida, baixa as alterações no cliente. Se você não controlar a identidade do cliente que fez uma alteração, a alteração será carregada no servidor e, em seguida, baixada novamente no cliente durante a mesma sessão de sincronização. Em alguns casos, essa repetição de alterações é aceitável, mas em outros casos não.
Como ocorre no controle de alterações, o Sync Framework não define especificamente como oferecer suporte ao controle de identidade; no entanto, há uma maneira comum de abordá-lo. Para cada tabela a ser sincronizada, você pode usar a seguinte abordagem:
Adicione uma coluna à tabela base que controla quem fez cada inserção.
Adicione uma coluna à tabela base que controle quem fez cada atualização.
Adicione uma coluna à tabela de marcas de exclusão que controle quem fez cada exclusão.
Essas colunas e tabelas são usadas junto com a propriedade ClientId para determinar qual cliente dez cada inserção, atualização ou exclusão. Na primeira vez em que uma tabela é sincronizada usando um método que não seja uma sincronização de instantâneo, o Sync Framework armazena um valor de GUID no cliente que identifica esse cliente. Essa ID é transmitida para DbServerSyncProvider de forma que possa ser usada pelas consultas de seleção e atualização em cada SyncAdapter. O valor da ID está disponível através da propriedade ClientId. Considere a consulta Transact-SQL a seguir:
SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM
Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor AND
InsertTimestamp <= @sync_new_received_anchor AND InsertId <>
@sync_client_id
Essa consulta é semelhante à consulta anterior que controla as inserções feitas no servidor. A instrução na cláusula WHERE
garante que as únicas inserções baixadas sejam aquelas que não foram feitas pelo cliente que está sendo sincronizado no momento.
O Sync Framework também permite que os aplicativos identifiquem clientes usando um inteiro no servidor em vez de um valor de GUID. Para obter mais informações, consulte Como usar variáveis de sessão.
Exemplos de preparação do servidor
Os exemplos a seguir mostram como configurar a tabela Sales.Customer
no banco de dados de exemplo do Sync Framework com a infraestrutura de controle para tratar o cenário de aplicativo mais complexo: operações bidirecionais de inserção, atualização e exclusão com detecção de conflitos. Cenários menos complexos não exigem a infraestrutura completa. Para obter mais informações, consulte "Requisitos do servidor para cenários de sincronização" anteriormente neste tópico. Para obter um script completo que cria os objetos neste exemplo e os objetos adicionais, consulte Scripts de instalação para tópicos de instruções do provedor de banco de dados. Para obter mais informações sobre como usar esses objetos, consulte Como especificar sincronização de instantâneo, de download, de carregamento e bidirecional.
Os exemplos desta seção executam as seguintes etapas ao preparar um servidor:
Verifique o esquema
Sales.Customer
. Determine se a tabela tem uma chave primária e quaisquer colunas que possam ser usadas para o controle de alterações.Adicione colunas para controlar quando e onde as inserções e atualizações são feitas.
Crie uma tabela de marcas de exclusão e adicione um gatilho à tabela
Sales.Customer
para popular a tabela de marcas de exclusão.
Verificando o esquema Sales.Customer
O exemplo de código a seguir mostra o esquema da tabela Sales.Customer
. A tabela tem uma chave primária na coluna CustomerId
e não tem nenhuma coluna que possa ser usada para controlar alterações.
CREATE TABLE SyncSamplesDb.Sales.Customer(
CustomerId uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(),
CustomerName nvarchar(100) NOT NULL,
SalesPerson nvarchar(100) NOT NULL,
CustomerType nvarchar(100) NOT NULL)
Adicionando colunas para controlar as operações de inserção e atualização
O exemplo de código a seguir adiciona quatro colunas: UpdateTimestamp
, InsertTimestamp
, UpdateId
e InsertId
. A coluna UpdateTimestamp
é uma coluna SQL Server timestamp
. Essa coluna é atualizada automaticamente quando a linha é atualizada. Conforme observado anteriormente, uma tabela pode ter apenas uma coluna timestamp
. Portanto a coluna InsertTimestamp
é uma coluna binary(8)
que possui um padrão de @@DBTS + 1
. O exemplo é adicionado ao valor retornado por @@DBTS
para que as colunas UpdateTimestamp
e InsertTimestamp
tenham o mesmo valor depois que uma inserção for executada. Se isso não for feito, parecerá como se cada linha tivesse sido atualizada depois de ela ter sido inserida.
A ID que o Sync Framework cria para cada cliente é uma GUID; portanto, as duas colunas de ID são colunas uniqueidentifier
. As colunas têm um padrão de 00000000-0000-0000-0000-000000000000
. Esse valor indica que o servidor executou a atualização ou inserção. Um último exemplo inclui uma coluna DeleteId
na tabela de marcas de exclusão.
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD UpdateTimestamp timestamp
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD InsertTimestamp binary(8) DEFAULT @@DBTS + 1
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD UpdateId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD InsertId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
Agora que as colunas foram adicionadas, o código de exemplo a seguir adiciona índices. Esses e outros índices no código de exemplo são criados nas colunas consultadas durante a sincronização. Os índices são adicionados para enfatizar que você deve considerar os índices ao implementar o controle de alterações no banco de dados do servidor. Certifique-se de equilibrar o desempenho do servidor em relação ao desempenho da sincronização.
CREATE NONCLUSTERED INDEX IX_Customer_UpdateTimestamp
ON Sales.Customer(UpdateTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_InsertTimestamp
ON Sales.Customer(InsertTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_UpdateId
ON Sales.Customer(UpdateId)
CREATE NONCLUSTERED INDEX IX_Customer_InsertId
ON Sales.Customer(InsertId)
Adicionando uma tabela de marcas de exclusão para controlar operações de exclusão
O exemplo de código a seguir cria uma tabela de marcas de exclusão que tem um índice clusterizado e um gatilho para popular a tabela. Quando ocorre uma operação de exclusão na tabela Sales.Customer
, o gatilho insere uma linha na tabela Sales.Customer_Tombstone
. Antes de executar uma operação de inserção, o gatilho verifica se a tabela Sales.Customer_Tombstone
já contém uma linha que tem uma chave primária de uma linha excluída. Isso ocorre quando uma linha foi excluída de Sales.Customer
, reinserida e excluída novamente. Se essa linha for detectada em Sales.Customer_Tombstone
, o gatilho excluirá a linha e a inserirá novamente. A coluna DeleteTimestamp
em Sales.Customer_Tombstone
também pode ser atualizada.
CREATE TABLE SyncSamplesDb.Sales.Customer_Tombstone(
CustomerId uniqueidentifier NOT NULL PRIMARY KEY NONCLUSTERED,
CustomerName nvarchar(100) NOT NULL,
SalesPerson nvarchar(100) NOT NULL,
CustomerType nvarchar(100) NOT NULL,
DeleteId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
DeleteTimestamp timestamp)
CREATE TRIGGER Customer_DeleteTrigger
ON SyncSamplesDb.Sales.Customer FOR DELETE
AS
BEGIN
SET NOCOUNT ON
DELETE FROM SyncSamplesDb.Sales.Customer_Tombstone
WHERE CustomerId IN (SELECT CustomerId FROM deleted)
INSERT INTO SyncSamplesDb.Sales.Customer_Tombstone (CustomerId, CustomerName, SalesPerson, CustomerType)
SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM deleted
SET NOCOUNT OFF
END
CREATE CLUSTERED INDEX IX_Customer_Tombstone_DeleteTimestamp
ON Sales.Customer_Tombstone(DeleteTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_Tombstone_DeleteId
ON Sales.Customer_Tombstone(DeleteId)