Rastreando o processo de sincronização
Este tópico mostra como usar a infraestrutura de rastreamento e a classe de rastreamento do Sync Framework. Os exemplos neste tópico se concentram nos seguintes eventos e tipos do Sync Framework:
O evento ApplyChangeFailed e o objeto DbApplyChangeFailedEventArgs.
Para obter informações sobre como executar o código de exemplo, consulte "Exemplo de aplicativos nos tópicos de instruções" em Sincronizando o SQL Server e o SQL Server Compact.
Noções básicas sobre o rastreamento nos serviços de sincronização
O rastreamento envolve registrar operações, dados e metadados de aplicativo e apresentar essas informações a um ouvinte. Um ouvinte costuma gravar informações de rastreamento em um arquivo, mas também pode trabalhar com elas de outras maneiras. O Sync Framework inclui rastreamento para os provedores de sincronização do banco de dados. Em aplicativos distribuídos, o rastreamento pode ser muito importante porque permite solucionar problemas que poderiam ser difíceis de descobrir.
No Sync Framework o rastreamento é formado pelos seguintes componentes:
Uma infraestrutura de rastreamento baseada na implementação de rastreamento .NET Framework, especificamente a classe TraceListener. As operações mais importantes dos provedores do banco de dados são rastreadas, e os principais metadados são fornecidos para um ou mais ouvintes.
O objeto SyncTracer. Ele permite determinar qual nível de rastreamento está habilitado e gravar mensagens na saída de rastreamento com base em eventos de aplicativo.
Além dos componentes de rastreamento oferecidos pelo Sync Framework, a solução de problemas geralmente envolve outras ferramentas, como um depurador ou o SQL Server Profiler. Por exemplo, a saída de rastreamento poderia incluir informações sobre um banco de dados do SQL Server. Nesse caso, você usaria o SQL Server Profiler para obter mais detalhes sobre a atividade de banco de dados gerada pelo provedor de sincronização do servidor.
Usando a infraestrutura de rastreamento
Por padrão, o rastreamento não está habilitado para aplicativos do Sync Framework. Para configurar o ouvinte de rastreamento, inclua um arquivo de configuração de aplicativo (AppName.exe.config) na mesma pasta do seu executável do aplicativo gerenciado. Para obter mais informações sobre esse arquivo, consulte a documentação do Visual Studio. Nesse arquivo, é possível adicionar um ouvinte, definir seu tipo e os parâmetros relacionados, remover um ouvinte ou limpar todos os ouvintes que anteriormente foram definidos pelo aplicativo. Para obter mais informações, consulte a documentação do .NET Framework sobre rastreamento. O arquivo de configuração deve ser parecido com o exemplo a seguir.
<configuration>
<system.diagnostics>
<switches>
<!-- 0-off, 1-error, 2-warn, 3-info, 4-verbose. -->
<add name="SyncTracer" value="3" />
</switches>
<trace autoflush="true">
<listeners>
<add name="TestListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="TraceSample.log"/>
</listeners>
</trace>
</system.diagnostics>
</configuration>
Os seguintes nós XML são incluídos no código de exemplo:
Um ouvinte do tipo System.Diagnostics.TextWriterTraceListener (TextWriterTraceListener), com o nome do arquivo de saída: TraceSample.log.
Uma opção chamada SyncTracer. Ela tem um valor igual a 3. A tabela a seguir mostra todos os valores e como eles estão relacionados com a saída de rastreamento.
Valor da opção
Nível de rastreamento
Saída
0
off
Nenhuma mensagem para os ouvintes de rastreamento.
1
error
Apenas mensagens de erro para os ouvintes de rastreamento.
2
warning
Mensagens de erro e de aviso para os ouvintes de rastreamento.
3
info
Mensagens informativas, de aviso e de erro para os ouvintes de rastreamento.
4
verbose
Todas as mensagens para os ouvintes de rastreamento.
O rastreamento está com um pouco de sobrecarga. Por isso, você deve equilibrar o rastreamento em relação aos requisitos de desempenho do aplicativo. Na maioria dos casos, as configurações info e verbose só são apropriadas durante o desenvolvimento e a solução de problemas do aplicativo.
O arquivo de configuração a seguir mostra um exemplo para dispositivos.
<configuration>
<traceSettings>
<add key ="FileLocation" value="TraceSample.log"/>
<add key ="LogLevel" value="4"/>
</traceSettings>
</configuration>
O arquivo deve ser denominado trace.config.txt e deve ser colocado no diretório do aplicativo no dispositivo. Se você incluir o arquivo em uma solução do Visual Studio, ele pode ser implantado com o aplicativo.
O segmento de arquivo abaixo é de um rastreamento que foi configurado usando um valor SyncTracer igual a 3. Cada linha começa com o tipo de saída. Nesse caso, toda a saída é INFO. Se ocorresse um erro, as linhas relevantes começariam com ERROR.
INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, Connecting to server using string: Data Source=localhost;Initial Catalog=SyncSamplesDb;Integrated Security=True
INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, ----- Server Enumerating Changes to Client for Group "Customer" -----
INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, Client Id: bc5610f6-bf9c-4ccd-8599-839e54e953e2
INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, Mapped Originator Id: 0
INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:042,
INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:042, ----- Enumerating Inserts for Table Customer -----
INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:058, Changes Enumerated: 5
INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:058, --- End Enumerating Inserts for Table Customer ---
Usando o objeto SyncTracer
O objeto SyncTracer permite gravar dados de rastreamento específicos do aplicativo no arquivo de rastreamento. Isso pode fornecer informações contextuais sobre a atividade de um aplicativo em um determinado horário. Este objeto permite executar as seguintes tarefas:
Determinar que nível de rastreamento está habilitado, usando estes métodos:
Gravar mensagens na saída de rastreamento com base em eventos de aplicativo, usando os métodos abaixo, e outras sobrecargas relacionadas a cada método:
Por exemplo, para gerar uma mensagem informativa, você pode usar o seguinte código:
SyncTracer.Info("Informational message")
Se o nível Informações estiver habilitado, a mensagem será gravada na saída; do contrário, será ignorada.
Mensagens mais complexas também podem ser formadas, como as mostradas a seguir. Os números especificados definem o nível de recuo do arquivo de saída.
SyncTracer.Verbose("Processing table t1")
SyncTracer.Verbose(1, "Applying Deletes")
SyncTracer.Verbose(2, "{0} rows deleted", numDeleted)
SyncTracer.Verbose(1, "Applying Inserts")
SyncTracer.Verbose(2, "{0} rows inserted", numInserted)
A saída é a seguinte:
Processing table t1
Applying Deletes
7 Rows Deleted
Applying Inserts
9 Rows inserted
O exemplo de código a seguir grava informações no console sobre quais níveis de rastreamento estão habilitados. O arquivo de configuração especifica um valor igual a 3 para a opção SyncTracer. Isso corresponde a Info. Portanto, Error, Warning e Info retornam True e Verbose retorna False.
Console.WriteLine("** Tracing Levels Enabled for this Application **");
Console.WriteLine("Error: " + SyncTracer.IsErrorEnabled().ToString());
Console.WriteLine("Warning: " + SyncTracer.IsWarningEnabled().ToString());
Console.WriteLine("Info: " + SyncTracer.IsInfoEnabled().ToString());
Console.WriteLine("Verbose: " + SyncTracer.IsVerboseEnabled().ToString());
Console.WriteLine("** Tracing Levels Enabled for this Application **")
Console.WriteLine("Error: " + SyncTracer.IsErrorEnabled().ToString())
Console.WriteLine("Warning: " + SyncTracer.IsWarningEnabled().ToString())
Console.WriteLine("Info: " + SyncTracer.IsInfoEnabled().ToString())
Console.WriteLine("Verbose: " + SyncTracer.IsVerboseEnabled().ToString())
O exemplo de código a seguir mostra como gravar mensagens de aviso formatadas sobre conflitos de dados na saída de rastreamento. Para obter mais informações sobre conflitos, consulte Como manipular conflitos de dados e erros de sincronização de bancos de dados (SQL Server). O rastreamento detalhado inclui informações sobre conflitos. Neste aplicativo, o rastreamento detalhado está desabilitado, e o aplicativo marca os conflitos com um aviso.
if (SyncTracer.IsVerboseEnabled() == false && e.Conflict.Type != DbConflictType.ErrorsOccurred)
{
DataTable conflictingClientChange = e.Conflict.LocalChange;
DataTable conflictingServerChange = e.Conflict.RemoteChange;
int serverColumnCount = conflictingServerChange.Columns.Count;
int clientColumnCount = conflictingClientChange.Columns.Count;
StringBuilder clientRowAsString = new StringBuilder();
StringBuilder serverRowAsString = new StringBuilder();
for (int i = 0; i < clientColumnCount; i++)
{
clientRowAsString.Append(conflictingClientChange.Rows[0][i] + " | ");
}
for (int i = 0; i < serverColumnCount; i++)
{
serverRowAsString.Append(conflictingServerChange.Rows[0][i] + " | ");
}
SyncTracer.Warning(1, "CONFLICT DETECTED FOR SESSION {0}", e.Session.SessionId);
SyncTracer.Warning(2, "** Client change **");
SyncTracer.Warning(2, clientRowAsString.ToString());
SyncTracer.Warning(2, "** Server change **");
SyncTracer.Warning(2, serverRowAsString.ToString());
}
If SyncTracer.IsVerboseEnabled() = False AndAlso e.Conflict.Type <> DbConflictType.ErrorsOccurred Then
Dim conflictingClientChange As DataTable = e.Conflict.LocalChange
Dim conflictingServerChange As DataTable = e.Conflict.RemoteChange
Dim serverColumnCount As Integer = conflictingServerChange.Columns.Count
Dim clientColumnCount As Integer = conflictingClientChange.Columns.Count
Dim clientRowAsString As New StringBuilder()
Dim serverRowAsString As New StringBuilder()
For i As Integer = 0 To clientColumnCount - 1
clientRowAsString.Append(Convert.ToString(conflictingClientChange.Rows(0)(i)) & " | ")
Next
For i As Integer = 0 To serverColumnCount - 1
serverRowAsString.Append(Convert.ToString(conflictingServerChange.Rows(0)(i)) & " | ")
Next
SyncTracer.Warning(1, "CONFLICT DETECTED FOR SESSION {0}", e.Session.SessionId)
SyncTracer.Warning(2, "** Client change **")
SyncTracer.Warning(2, clientRowAsString.ToString())
SyncTracer.Warning(2, "** Server change **")
SyncTracer.Warning(2, serverRowAsString.ToString())
End If
Verificar quais níveis de rastreamento estão habilitados pode ajudar você a evitar um processamento possivelmente dispendioso. No código do exemplo, o aplicativo evita processamento adicional se o rastreamento detalhado já está habilitado. De modo contrário, o aplicativo só poderá gerar saída quando um certo nível de rastreamento estiver habilitado.
Considerações de segurança do rastreamento
Os arquivos de rastreamento podem incluir informações sobre computadores cliente e servidor, dados de aplicativo e logons. (As senhas não são gravadas no arquivo de rastreamento.) Se o rastreamento detalhado estiver habilitado, cada linha alterada do banco de dados será gravada no arquivo de rastreamento. Para ajudar a proteger o arquivo de rastreamento, use as listas de controle de acesso apropriadas.
Exemplo de código completo
O exemplo de código completo a seguir inclui os exemplos de código descritos anteriormente neste tópico e código adicional para executar a sincronização. Antes de executar o aplicativo, execute estas etapas:
Crie um projeto no Visual Studio.
Adicione referências às DLLs do Sync Framework e à classe Utility que está disponível em Classe de utilitário para tópicos de instruções do provedor de banco de dados.
Crie um arquivo de configuração e copie o código XML do exemplo mostrado anteriormente neste tópico.
Conheça as chamadas para métodos na classe Utility:
Utility.MakeFailingChangeOnNode(Utility.ConnStr_SqlSync_Client) Isso faz uma alteração no cliente que falha quando aplicada ao servidor. A violação de restrição e a exceção de aplicativo relacionada são gravadas automaticamente no arquivo de rastreamento como avisos.
Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Client, "Customer") e Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Server, "Customer") Eles fazem alterações no cliente e no servidor que entram em conflito quando são sincronizadas. Os conflitos são gravados no arquivo de rastreamento, no manipulador de eventos SampleSyncProvider_ApplyChangeFailed.
Depois de executar o aplicativo, abra o arquivo de rastreamento de saída para ver as mensagens gravadas automaticamente e os avisos de conflito, que são o resultado do código de aplicativo.
using System;
using System.IO;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.SqlServer;
using Microsoft.Synchronization.Data.SqlServerCe;
namespace Microsoft.Samples.Synchronization
{
class Program
{
static void Main(string[] args)
{
// Specify the connections to the SQL Server and SQL Server Compact databases.
SqlConnection serverConn = new SqlConnection(Utility.ConnStr_SqlSync_Server);
SqlConnection clientConn = new SqlConnection(Utility.ConnStr_SqlSync_Client);
//Write to the console which tracing levels are enabled. The app.config
//file specifies a value of 3 for the SyncTracer switch, which corresponds
//to Info. Therefore, Error, Warning, and Info return True, and Verbose
//returns False.
Console.WriteLine("");
Console.WriteLine("** Tracing Levels Enabled for this Application **");
Console.WriteLine("Error: " + SyncTracer.IsErrorEnabled().ToString());
Console.WriteLine("Warning: " + SyncTracer.IsWarningEnabled().ToString());
Console.WriteLine("Info: " + SyncTracer.IsInfoEnabled().ToString());
Console.WriteLine("Verbose: " + SyncTracer.IsVerboseEnabled().ToString());
// Create a scope named "customers" and add a table to it.
DbSyncScopeDescription scopeDesc = new DbSyncScopeDescription("customers");
DbSyncTableDescription customerDescription =
SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn);
scopeDesc.Tables.Add(customerDescription);
// Create a provisioning object for "customers". We specify that
// all synchronization-related objects should be created in a
// database schema named "Sync". If you specify a schema, it must already exist
// in the database.
SqlSyncScopeProvisioning serverConfig = new SqlSyncScopeProvisioning(serverConn, scopeDesc);
serverConfig.ObjectSchema = "Sync";
// Configure the scope and change-tracking infrastructure.
serverConfig.Apply();
// Provision the client database.
SqlSyncScopeProvisioning clientConfig = new SqlSyncScopeProvisioning(clientConn, scopeDesc);
clientConfig.ObjectSchema = "Sync";
clientConfig.Apply();
// Initial synchronization session.
SampleSyncOrchestrator syncOrchestrator;
SyncOperationStatistics syncStats;
// Data is downloaded from the server to the client.
syncOrchestrator = new SampleSyncOrchestrator(
new SqlSyncProvider("customers", clientConn, null, "Sync"),
new SqlSyncProvider("customers", serverConn, null, "Sync")
);
syncStats = syncOrchestrator.Synchronize();
syncOrchestrator.DisplayStats(syncStats, "initial");
//Make a change at the client that fails when it is
//applied at the servr. The constraint violation
//is automatically written to the trace file as a warning.
Utility.MakeFailingChangeOnNode(Utility.ConnStr_SqlSync_Client);
//Make changes at the client and server that conflict
//when they are synchronized. The conflicts are written
//to the trace file in the SampleSyncProvider_ApplyChangeFailed
//event handler.
Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Client, "Customer");
Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Server, "Customer");
SqlSyncProvider clientProv = new SqlSyncProvider("customers", clientConn, null, "Sync");
clientProv.ApplyChangeFailed += new EventHandler<DbApplyChangeFailedEventArgs>(SampleSyncProvider_ApplyChangeFailed);
SqlSyncProvider serverProv = new SqlSyncProvider("customers", serverConn, null, "Sync");
serverProv.ApplyChangeFailed += new EventHandler<DbApplyChangeFailedEventArgs>(SampleSyncProvider_ApplyChangeFailed);
// Synchronize the two databases.
syncOrchestrator = new SampleSyncOrchestrator(clientProv, serverProv);
syncStats = syncOrchestrator.Synchronize();
syncOrchestrator.DisplayStats(syncStats, "subsequent");
serverConn.Close();
serverConn.Dispose();
clientConn.Close();
clientConn.Dispose();
Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Server);
Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Client);
Console.Write("\nPress any key to exit.");
Console.Read();
}
static void SampleSyncProvider_ApplyChangeFailed(object sender, DbApplyChangeFailedEventArgs e)
{
//Verbose tracing includes information about conflicts. In this application,
//Verbose tracing is disabled, and we have decided to flag conflicts with a
//warning.
//Check if Verbose tracing is enabled and if the conflict is an error.
//If the conflict is not an error, we write a warning to the trace file
//with information about the conflict.
if (SyncTracer.IsVerboseEnabled() == false && e.Conflict.Type != DbConflictType.ErrorsOccurred)
{
DataTable conflictingClientChange = e.Conflict.LocalChange;
DataTable conflictingServerChange = e.Conflict.RemoteChange;
int serverColumnCount = conflictingServerChange.Columns.Count;
int clientColumnCount = conflictingClientChange.Columns.Count;
StringBuilder clientRowAsString = new StringBuilder();
StringBuilder serverRowAsString = new StringBuilder();
for (int i = 0; i < clientColumnCount; i++)
{
clientRowAsString.Append(conflictingClientChange.Rows[0][i] + " | ");
}
for (int i = 0; i < serverColumnCount; i++)
{
serverRowAsString.Append(conflictingServerChange.Rows[0][i] + " | ");
}
SyncTracer.Warning(1, "CONFLICT DETECTED FOR SESSION {0}", e.Session.SessionId);
SyncTracer.Warning(2, "** Client change **");
SyncTracer.Warning(2, clientRowAsString.ToString());
SyncTracer.Warning(2, "** Server change **");
SyncTracer.Warning(2, serverRowAsString.ToString());
}
}
}
public class SampleSyncOrchestrator : SyncOrchestrator
{
public SampleSyncOrchestrator(RelationalSyncProvider localProvider, RelationalSyncProvider remoteProvider)
{
this.LocalProvider = localProvider;
this.RemoteProvider = remoteProvider;
this.Direction = SyncDirectionOrder.UploadAndDownload;
}
public void DisplayStats(SyncOperationStatistics syncStatistics, string syncType)
{
Console.WriteLine(String.Empty);
if (syncType == "initial")
{
Console.WriteLine("****** Initial Synchronization ******");
}
else if (syncType == "subsequent")
{
Console.WriteLine("***** Subsequent Synchronization ****");
}
Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
Console.WriteLine("Total Changes Uploaded: " + syncStatistics.UploadChangesTotal);
Console.WriteLine("Total Changes Downloaded: " + syncStatistics.DownloadChangesTotal);
Console.WriteLine("Complete Time: " + syncStatistics.SyncEndTime);
Console.WriteLine(String.Empty);
}
}
}
Imports System.IO
Imports System.Text
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlServerCe
Imports Microsoft.Synchronization
Imports Microsoft.Synchronization.Data
Imports Microsoft.Synchronization.Data.SqlServer
Imports Microsoft.Synchronization.Data.SqlServerCe
Namespace Microsoft.Samples.Synchronization
Class Program
Public Shared Sub Main(ByVal args As String())
' Specify the connections to the SQL Server and SQL Server Compact databases.
Dim serverConn As New SqlConnection(Utility.ConnStr_SqlSync_Server)
Dim clientConn As New SqlConnection(Utility.ConnStr_SqlSync_Client)
'Write to the console which tracing levels are enabled. The app.config
'file specifies a value of 3 for the SyncTracer switch, which corresponds
'to Info. Therefore, Error, Warning, and Info return True, and Verbose
'returns False.
Console.WriteLine("")
Console.WriteLine("** Tracing Levels Enabled for this Application **")
Console.WriteLine("Error: " + SyncTracer.IsErrorEnabled().ToString())
Console.WriteLine("Warning: " + SyncTracer.IsWarningEnabled().ToString())
Console.WriteLine("Info: " + SyncTracer.IsInfoEnabled().ToString())
Console.WriteLine("Verbose: " + SyncTracer.IsVerboseEnabled().ToString())
' Create a scope named "customers" and add a table to it.
Dim scopeDesc As New DbSyncScopeDescription("customers")
Dim customerDescription As DbSyncTableDescription = SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn)
scopeDesc.Tables.Add(customerDescription)
' Create a provisioning object for "customers". We specify that
' all synchronization-related objects should be created in a
' database schema named "Sync". If you specify a schema, it must already exist
' in the database.
Dim serverConfig As New SqlSyncScopeProvisioning(serverConn, scopeDesc)
serverConfig.ObjectSchema = "Sync"
' Configure the scope and change-tracking infrastructure.
serverConfig.Apply()
' Provision the client database.
Dim clientConfig As New SqlSyncScopeProvisioning(clientConn, scopeDesc)
clientConfig.ObjectSchema = "Sync"
clientConfig.Apply()
' Initial synchronization session.
Dim syncOrchestrator As SampleSyncOrchestrator
Dim syncStats As SyncOperationStatistics
' Data is downloaded from the server to the client.
syncOrchestrator = New SampleSyncOrchestrator(New SqlSyncProvider("customers", clientConn, Nothing, "Sync"), New SqlSyncProvider("customers", serverConn, Nothing, "Sync"))
syncStats = syncOrchestrator.Synchronize()
syncOrchestrator.DisplayStats(syncStats, "initial")
'Make a change at the client that fails when it is
'applied at the servr. The constraint violation
'is automatically written to the trace file as a warning.
Utility.MakeFailingChangeOnNode(Utility.ConnStr_SqlSync_Client)
'Make changes at the client and server that conflict
'when they are synchronized. The conflicts are written
'to the trace file in the SampleSyncProvider_ApplyChangeFailed
'event handler.
Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Client, "Customer")
Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Server, "Customer")
Dim clientProv As New SqlSyncProvider("customers", clientConn, Nothing, "Sync")
AddHandler clientProv.ApplyChangeFailed, AddressOf SampleSyncProvider_ApplyChangeFailed
Dim serverProv As New SqlSyncProvider("customers", serverConn, Nothing, "Sync")
AddHandler serverProv.ApplyChangeFailed, AddressOf SampleSyncProvider_ApplyChangeFailed
' Synchronize the two databases.
syncOrchestrator = New SampleSyncOrchestrator(clientProv, serverProv)
syncStats = syncOrchestrator.Synchronize()
syncOrchestrator.DisplayStats(syncStats, "subsequent")
serverConn.Close()
serverConn.Dispose()
clientConn.Close()
clientConn.Dispose()
Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Server)
Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Client)
Console.Write(vbLf & "Press any key to exit.")
Console.Read()
End Sub
Private Shared Sub SampleSyncProvider_ApplyChangeFailed(ByVal sender As Object, ByVal e As DbApplyChangeFailedEventArgs)
'Verbose tracing includes information about conflicts. In this application,
'Verbose tracing is disabled, and we have decided to flag conflicts with a
'warning.
'Check if Verbose tracing is enabled and if the conflict is an error.
'If the conflict is not an error, we write a warning to the trace file
'with information about the conflict.
If SyncTracer.IsVerboseEnabled() = False AndAlso e.Conflict.Type <> DbConflictType.ErrorsOccurred Then
Dim conflictingClientChange As DataTable = e.Conflict.LocalChange
Dim conflictingServerChange As DataTable = e.Conflict.RemoteChange
Dim serverColumnCount As Integer = conflictingServerChange.Columns.Count
Dim clientColumnCount As Integer = conflictingClientChange.Columns.Count
Dim clientRowAsString As New StringBuilder()
Dim serverRowAsString As New StringBuilder()
For i As Integer = 0 To clientColumnCount - 1
clientRowAsString.Append(Convert.ToString(conflictingClientChange.Rows(0)(i)) & " | ")
Next
For i As Integer = 0 To serverColumnCount - 1
serverRowAsString.Append(Convert.ToString(conflictingServerChange.Rows(0)(i)) & " | ")
Next
SyncTracer.Warning(1, "CONFLICT DETECTED FOR SESSION {0}", e.Session.SessionId)
SyncTracer.Warning(2, "** Client change **")
SyncTracer.Warning(2, clientRowAsString.ToString())
SyncTracer.Warning(2, "** Server change **")
SyncTracer.Warning(2, serverRowAsString.ToString())
End If
End Sub
End Class
Public Class SampleSyncOrchestrator
Inherits SyncOrchestrator
Public Sub New(ByVal localProvider As RelationalSyncProvider, ByVal remoteProvider As RelationalSyncProvider)
Me.LocalProvider = localProvider
Me.RemoteProvider = remoteProvider
Me.Direction = SyncDirectionOrder.UploadAndDownload
End Sub
Public Sub DisplayStats(ByVal syncStatistics As SyncOperationStatistics, ByVal syncType As String)
Console.WriteLine([String].Empty)
If syncType = "initial" Then
Console.WriteLine("****** Initial Synchronization ******")
ElseIf syncType = "subsequent" Then
Console.WriteLine("***** Subsequent Synchronization ****")
End If
Console.WriteLine("Start Time: " & Convert.ToString(syncStatistics.SyncStartTime))
Console.WriteLine("Total Changes Uploaded: " & Convert.ToString(syncStatistics.UploadChangesTotal))
Console.WriteLine("Total Changes Downloaded: " & Convert.ToString(syncStatistics.DownloadChangesTotal))
Console.WriteLine("Complete Time: " & Convert.ToString(syncStatistics.SyncEndTime))
Console.WriteLine([String].Empty)
End Sub
End Class
End Namespace