Пошаговое руководство: расширение локального кэша базы данных для поддержки двунаправленной синхронизации
В Visual Studio 2008 Кэш локальной базы данных применяется для настройки базы данных SQL Server Compact и набора разделяемых классов, которые обеспечивают работу со службами Sync Framework. Так как Visual Studio формирует разделяемые классы, то можно написать код для добавления функции синхронизации и вместе с тем сохранить возможность просмотра и изменения настроек в диалоговом окне Настройка синхронизации данных. Дополнительные сведения о кэше локальной базы данных и разделяемых классах см. в документации по Visual Studio 2008.
По умолчанию диалоговое окно Настройка синхронизации данных позволяет настраивать службы Sync Framework только для сценариев загрузки. Это означает, что после настройки синхронизации данных вызов Synchronize загружает из серверной базы данных в клиентскую только изменения. Один из наиболее распространенных способов расширения кода синхронизации состоит в настройке двунаправленной синхронизации. Это позволит передавать изменения от клиента серверу. Для включения двунаправленной синхронизации рекомендуется расширить сформированный код следующими способами.
Установите направление синхронизации на двунаправленное.
Добавьте код для обработки конфликтов синхронизации.
Удалите столбцы отслеживания сервера из команд синхронизации.
Примечание. |
---|
Visual Studio 2008 использует службы Sync Framework для ADO.NET версии 1.0 при формировании кода для кэша локальной базы данных. |
Предварительные требования
До того как приступить к выполнению данного пошагового руководства, необходимо выполнить следующее пошаговое руководство из документации по Visual Studio 2008: «Пошаговое руководство: создание приложения без постоянного соединения». Результатом выполнения данного пошагового руководства будет проект, который содержит кэш локальной базы данных и приложение Windows Form, позволяющее загружать изменения из таблицы Customers базы данных Northwind в базу данных SQL Server Compact. Теперь все готово к загрузке данного решения пошагового руководства и добавлению функциональных возможностей двунаправленной синхронизации.
Открытие решения OCSWalkthrough
Откройте Visual Studio.
В меню Файл откройте существующее решение или проект и найдите решение OCSWalkthrough. Это файл OCSWalkthrough.sln.
Установка направления синхронизации
Диалоговое окно Настройка синхронизации данных устанавливает свойство SyncDirection либо в значение DownloadOnly, либо в значение Snapshot. Чтобы включить двунаправленную синхронизацию, установите свойство SyncDirection в значение Bidirectional для каждой таблицы, которую требуется включить для передачи изменений.
Установка направления синхронизации
Щелкните правой кнопкой мыши файл NorthwindCache.sync и выберите Просмотреть код. При первом выполнении данной операции Visual Studio создает файл класса NorthwindCache на узле NorthwindCache.sync в Обозревателе решений. Данный файл содержит разделяемый класс
NorthwindCacheSyncAgent
, и при необходимости можно добавлять в него другие классы.В файле класса NorthwindCache добавьте строку кода в метод
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
Откройте форму Form1 в редакторе кода.
В файле формы Form1 отредактируйте код в обработчике события
SynchronizeButton_Click
, чтобы он включал статистику по передаче и загрузке: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)
Синхронизация и просмотр статистики
Нажмите клавишу F5.
Обновите запись в форме и на панели инструментов нажмите кнопку Сохранить.
Выберите пункт Синхронизировать сейчас.
Появится окно сообщений, содержащее сведения о синхронизированных записях. Статистика показывает, что одна строка была передана и одна загружена, даже если на сервере не было изменений. Дополнительная загрузка происходит вследствие того, что изменения клиента возвращаются в клиент после их применения на сервере. Дополнительные сведения см. в подразделе «Определение клиента, выполнившего изменение данных» в разделе Как использовать пользовательскую систему отслеживания изменений.
Чтобы закрыть окно сообщения, нажмите кнопку ОК, но не завершайте работу приложения.
Синхронизация и просмотр устранения конфликтов
Обновите запись в форме и нажмите кнопку Сохранить.
При работающем приложении используйте Обозреватель серверов или Обозреватель баз данных (или другое средство управления базами данных) для соединения с базой данных сервера.
Для демонстрации поведения по умолчанию при устранении конфликтов в Обозревателе серверов или Обозревателе баз данных обновите ту же запись, которая была обновлена в форме, установив другое значение, и зафиксируйте изменение (перейдите с измененной строки).
Вернитесь в форму и нажмите Синхронизировать сейчас.
Проверьте обновление в сетке приложения и базе данных сервера. Обратите внимание, что обновление, сделанное на сервере, перезаписывает обновление на клиенте. Сведения о том, как изменить поведение устранения конфликтов, см. в следующем подразделе данного раздела — «Добавление кода для обработки конфликтов синхронизации».
Добавление кода для обработки конфликтов синхронизации
В службах Sync Framework строка находится в конфликте, если произошло ее изменение на клиенте и на сервере между синхронизациями. Службы Sync Framework предоставляют набор функций, которые можно использовать для определения и разрешения конфликтов. В данном пошаговом руководстве добавляется базовая обработка конфликтов, связанных с тем, что одна и та же строка обрабатывается на клиенте и сервере. К другим видам конфликтов относится удаление строки в одной базе данных и обновление в другой или вставка в обе базы данных строк с дублирующимися первичными ключами. Дополнительные сведения об обнаружении и разрешении конфликтов см. в разделе Как обрабатывать конфликты и ошибки в данных.
Добавление обработки конфликтов
Добавьте код для обработки события сервера ApplyChangeFailed и события клиента ApplyChangeFailed. Данные события запускаются, когда строка не может быть применена вследствие конфликта или ошибки. Методы, которые обрабатывают данные события в образце кода, устанавливают тип конфликта и указывают, что конфликты обновления клиента или обновления сервера должны быть исправлены путем принудительной записи изменений клиента в базу данных сервера. Команда синхронизации, которая применяет обновления к базе данных сервера, включает средства определения того, когда следует принудительно вносить изменения. Данная команда включается в код в следующем подразделе этого раздела — «Удаление столбцов отслеживания сервера из команд синхронизации».
Примечание. Образец кода содержит базовый пример обработки конфликта. Способ обработки конфликтов зависит от требований приложения и бизнес-логики.
Добавление кода на языке C# происходит иначе по сравнению с Visual Basic.
При использовании языка C# добавьте код в файлы NorthwindCache.cs и Form1.cs. В файле NorthwindCache.cs добавьте следующий код после класса
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; } } }
В файле Form1.cs отредактируйте код в обработчике события
SynchronizeButton_Click
, чтобы он вызывал методAddHandlers
, который был добавлен в файл NorthwindCache.cs в предыдущем шаге:NorthwindCacheSyncAgent syncAgent = new NorthwindCacheSyncAgent(); NorthwindCacheClientSyncProvider clientSyncProvider = (NorthwindCacheClientSyncProvider)syncAgent.LocalProvider; clientSyncProvider.AddHandlers(); Microsoft.Synchronization.Data.SyncStatistics syncStats = syncAgent.Synchronize();
Для Visual Basic в файле NorthwindCache.vb добавьте следующий код после инструкции
End Class
для классаNorthwindCacheSyncAgent
.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
Синхронизация и просмотр устранения конфликтов
Нажмите клавишу F5.
Обновите запись в форме и нажмите кнопку Сохранить.
В Обозревателе серверов/Проводнике баз данных обновите ту же запись, которая была обновлена в форме, установив другое значение, и зафиксируйте изменение.
Вернитесь в форму и нажмите Синхронизировать сейчас.
Проверьте обновление в сетке приложения и базе данных сервера. Обратите внимание, что обновление, сделанное на клиенте, перезаписывает обновление на сервере.
Удаление столбцов отслеживания сервера из команд синхронизации
После создания кэша локальной базы данных столбцы, используемые для отслеживания изменений в базе данных сервера, загружаются на клиент (в данном пошаговом руководстве это столбцы CreationDate
и LastEditDate
). Для поддержки двунаправленной синхронизации и гарантирования конвергенции данных на клиенте и сервере удалите эти столбцы из команд SQL, которые применяют изменения к базе данных сервера. Также можно удалить их из команд, которые выбирают изменения сервера, применяемые на клиенте, но это необязательно. Вследствие ограничений, касающихся некоторых изменений схем в клиентской базе данных, эти столбцы нельзя удалить. Дополнительные сведения о командах синхронизации см. в разделе Как задать синхронизацию моментальными снимками, с загрузкой, с передачей и двунаправленную.
Примечание. |
---|
При использовании отслеживания изменений SQL Server отслеживаемые столбцы не добавляются в таблицы. В этом случае редактирование команд, которые применяют изменения на сервере, не требуется. |
Удаление столбцов отслеживания из команд синхронизации
Добавьте следующий код в класс
NorthwindCache
(файл NorthwindCache.vb или NorthwindCache.cs) после инструкцииEnd Class
для классаNorthwindCacheServerSyncProvider
. Данный код переопределяет две команды, установленные в качестве свойств объекта SyncAdapter для таблицыCustomers
: свойства InsertCommand и UpdateCommand. Команды, сформированные с помощью диалогового окна Настройка синхронизации данных, содержат ссылки на столбцыCreationDate
иLastEditDate
. Данные команды были переопределены в методеOnInitialized
классаCustomersSyncAdapter
. Свойство DeleteCommand не переопределяется, так как оно не влияет на столбцыCreationDate
иLastEditDate
.Переменные в каждой команде SQL используются для передачи данных и метаданных между службами Sync Framework, клиентом и сервером. Следующие переменные сеанса используются в командах, приведенных ниже.
@sync_row_count
. Возвращает число строк, затронутых последней операцией на сервере. В базах данных SQL Server значение этой переменной представляет функция @@ROWCOUNT.@sync_force_write
. Используется для принудительного применения изменения, которое завершилось со сбоем вследствие конфликта или ошибки.@sync_last_received_anchor
. Используется для определения набора изменений, которые должны быть синхронизированы в течение сеанса.
Дополнительные сведения о переменных сеанса см. в разделе Как использовать переменные сеанса.
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
Синхронизация и просмотр обновлений отслеживаемого столбца
Нажмите клавишу F5.
В форме обновите запись путем изменения значения в столбце
LastEditDate
и нажмите кнопку Сохранить.Вернитесь в форму и нажмите Синхронизировать сейчас.
Проверьте обновление в сетке приложения и базе данных сервера. Обратите внимание, что значение столбца, полученное с сервера, перезаписывает обновление на клиенте. Применяется следующий процесс обновления.
Службы Sync Framework определяют, что на клиенте изменена строка.
Во время синхронизации строка обновляется и применяется к таблице базы данных сервера. Однако столбцы отслеживания не включаются в инструкцию обновления. Фактически службы Sync Framework выполняют «фиктивное обновление» таблицы.
Строка возвращается на клиент, и команды, выбирающие изменения сервера, включают столбцы отслеживания. Таким образом, изменение, сделанное на клиенте, перезаписывается значением, полученным с сервера.
Заключение
В данном пошаговом руководстве производилась настройка двунаправленной синхронизации с базовой обработкой конфликтов и затрагивалась потенциальная проблема наличия столбцов отслеживания сервера в клиентской базе данных. С помощью разделяемых классов можно расширить код кэша локальной базы данных другими эффективными способами. Например, можно переопределить команды SQL, которые выбирают изменения из базы данных сервера, чтобы производилась фильтрация данных, загружаемых на клиент. Рекомендуется изучить инструкции, приведенные в данной документации, чтобы ознакомиться со способами, с помощью которых можно добавлять или изменять код синхронизации для удовлетворения требований приложений. Дополнительные сведения см. в разделе Программирование распространенных задач синхронизации клиента и сервера.
См. также
Основные положения
Программирование распространенных задач синхронизации клиента и сервера
Средства для облегчения разработки приложений