Продолжается подписка на наши издания! Вы не забыли подписаться?

Microsoft Sync Framework

Опубликовано: 22.04.2008

Общие концепции

Microsoft Sync Framework (MSF) – это платформа для разработки решений, реализующих различные сценарии синхронизации, появившаяся в SQL Server 2008.

Архитектура Sync Framework позволяет обмениваться данными любому количеству устройств, сервисов и экземпляров приложений, рассматривая хранилища данных, механизмы передачи и схемы как стандартные блоки. Синхронизируемые хранилища данных в терминологии Sync Framework называются копиями данных (или репликами; в данной статье они будут называться копиями данных или просто копиями). Блоки Sync Framework – это рантайм, сервисы метаданных и провайдеры. Рантайм производит синхронизацию копий данных согласно указаниям провайдеров. Провайдеры используют сервисы метаданных для обработки и хранения метаданных.

На рисунке 1 показана применяемая в Sync Framework схема организации синхронизации. Синхронизацию выполняет объект сессии синхронизации, используя интерфейсы провайдеров хранилищ данных. Например, если нужно синхронизировать контакты между мобильным устройством и хранилищем данных, создается сессия синхронизации, соединяющая провайдеров этих устройств, и определяющая наличие изменений, используя сервисы метаданных.


Рисунок 1.

Участники синхронизации

Участник синхронизации – это комбинация провайдера и ассоциированной с ним копии данных.

MSF поддерживает три типа участников синхронизации: полный, частичный и простой. Тип участника определяется его способностью сохранять и обрабатывать метаданные.

Полные участники

Полные участники – это способные хранить данные устройства, для которых можно разрабатывать приложения. Примерами таких устройств могут быть ноутбуки или смартфоны, способные самостоятельно исполнять приложения, а также сохранять необходимые для синхронизации данные.

Частичные участники

Частичные участники – это устройства, способные хранить данные, но неспособные исполнять приложения. Важно то, что они могут хранить метаданные синхронизации, что позволяет синхронизировать их с любым полным участником. Примером такого устройства может быть флешка или MP3-плейер.

Простые участники

Простые участники – это устройства, способные только выдавать информацию по запросу. Они неспособны сохранять новые данные и манипулировать ими, и не поддерживают создания новых приложений. Простой участник в хранении метаданных полагается на полного участника.

Примерами простых участников могут служить Web-сервисы сторонних организаций. Вы можете обращаться к этим Web-сервисам и получать результаты, но возможности создать собственное хранилище данных или выполнить свое приложение они вам не предоставят.

Состав Microsoft Sync Framework

Microsoft Sync Framework включает:

MSF предоставляет разработчикам приложений синхронизации:

Метаданные

Метаданные – основа Microsoft Sync Framework, как и любого другого продукта для синхронизации данных. Отслеживание изменений основывается в MSF реализуется простым сравнением текущего состояния данных с ранее сохраненным состоянием. Для каждой синхронизируемой копии или элемента данных должен существовать определенный набор метаданных.

MSF разрешает использовать любой формат метаданных. Тем не менее, существуют рекомендации по формату метаданных, приведенные ниже.

Сведения (knowledge)

Такие типичные операции синхронизации, как перечисление изменений и выявление конфликтов, требуют сравнения версий элементов синхронизируемых копий данных. Для этого придется передать копии назначения все версии элементов копии-источника. Это очень неэффективно.

В целях оптимизации было принято решение сжать все номера версий в одну компактную структуру данных, получившую название knowledge, или, по-русски, сведения. Эти сведения отражают все изменения (то есть содержат версии всех элементов), известные этой копии данных. Обычно сведения очень компактны. Однако они не могут полностью заменить метаданные элементов, поскольку по-прежнему нужно знать, когда и как изменялся тот или иной элемент.

Сведения могут содержать следующие вхождения:

  1. Scope clock vector.
  2. Range exceptions.
  3. Single item или change unit exceptions.

Scope clock vector определяет наиболее компактное представление сведений, при котором их размер пропорционален числу синхронизируемых копий. Scope clock vector состоит из элементов, которые называются clock vector elements. За всеми этими мудреными словами скрывается довольно простая вещь. Элемент – сочетание ключа копии данных и временной метки (в терминах MSF – tick count), определяющей версию элемента, а clock vector – это множество таких пар.

Каждый clock vector содержит как минимум один элемент, соответствующий локальной копии. Поскольку разные копии имеют разные и уникальные ID, эти элементы тоже являются уникальными для каждой копии.

Range exceptions используются для ассоциирования clock vector с диапазонами элементов, т.е. для элементов копии, имеющих значения ID, попадающие в указанный интервал, сведения будут иметь значения, равные clock vector-у range exception-а. Как несложно представить, range exceptions уменьшают объем структуры сведений, поскольку обычно указывают clock vector сразу для многих синхронизируемых элементов.

Single item exceptions используются для ассоциирования clock vector-а с одним элементом или единичным изменением. Это наименее компактное представление сведений, используемое только если невозможно использовать другие

Если жизнь хороша, и никаких конфликтов, ошибок или прерываний синхронизации нет, сведения остаются компактными и содержат только scope clock vector. В противном случае сведения становятся фрагментированными, в них появляются range exceptions и single item exceptions. Эти исключения указывают на отклонения от scope clock vector для конкретных элементов, единичных изменений или диапазонов элементов. Заметьте, что эти исключения не постоянны и исчезают после удачной синхронизации или разрешения конфликта.

Идентификаторы копий и элементов

Идентификаторы копий и элементов могут иметь как фиксированную, так и переменную длину. ID фиксированной длины представляются в виде байтового массива, а переменной – классом SyncId в управляемом, и структурой SYNC_ID – в неуправляемом коде.

Класс SyncIdFormatGroup в управляемом коде и структура ID_PARAMETERS в неуправляемом содержат схему формата ID. Эта схема говорит, имеет ли ID фиксированную или переменную длину, а также содержит размер ID, или, в случае переменной длины, максимальный размер ID. Эта схема используется многими методами MSF для проверки соответствия форматов ID, используемых разными синхронизируемыми объектами. Схема запрашивается у обоих провайдеров, участвующих в сессии синхронизации. Полученные схемы сравниваются, и, если они не совпадают, сессия не создается.

При синхронизации иерархических данных может оказаться полезным использовать в качестве префикса ID увеличивающееся число, например, текущее время. Поскольку родительские элементы обычно создаются раньше потомков, они будут встречаться в списке элементов раньше.

Создаваемым элементам можно присваивать глобальные идентификаторы, используя тот же способ, независимо от их контейнеров или вложенных элементов.

Обязательные метаданные копии

Каждая копия данных должна хранить некоторый обязательный набор метаданных, описанный ниже.

ID копии

Уникальный идентификатор копии данных. Это может быть любой идентификатор, но Microsoft рекомендует использовать 16-байтный GUID. Формат идентификатора копии, передаваемый методу MSF, должен соответствовать формату, определенному в провайдере. В управляемом коде формат указывается в свойстве IdFormats, а в неуправляемом – в структуре ID_PARAMETERS.

Current tick count

Текущий счетчик тиков копии. Это число можно получить от наиболее поздней локальной версии или еще каким-либо способом. Например, копия может использовать текущее время, если гарантируется, что его значение не будет сброшено.

Отображение ключа копии (Replica key map)

Отображение между ID копии и 4-байтными ключами. Из-за постоянных повторов идентификатора копии в метаданных рекомендуется использовать таблицу, в которой идентификаторы копий отображаются на 4-байтные ключи. Эти ключи впоследствии используются для ссылок на конкретные копии.

Текущие сведения

Сведения о текущих версиях элементов копии. С этими сведениями работают сервисы метаданных, лучше не трогать их руками.

Забытые сведения (Forgotten knowledge)

Позволяют копии определить, что копия-партнер не знает об удаленных элементах. Такое может случиться, если были удалены записи об этих элементах. Этими сведениями тоже должны оперировать сервисы метаданных.

Журнал конфликтов

Журнал с записями об обнаруженных, но не разрешенных конфликтах. Копии обязаны вести такие журналы. Форма представления конфликта зависит от вида хранилища данных. MSF должен иметь доступ к этим записям, чтобы сообщать пользователю о необходимости вмешательства в случае невозможности автоматического разрешения конфликта.

Кладбищенская книга (Tombstone log)

Информацию об удаленных из копии данных с черным юмором называют “tombstones”. На русский язык это переводится как «надгробие» или (велик и могуч английский язык) «длинный список участников проекта». Журнал, в котором перечислены такие надгробия, логично назвать кладбищенской книгой, или, вспоминая, что русский язык тоже велик и могуч, поминальничком. Этот журнал, как уже понятно, хранит информацию об элементах, удаленных из копии, что необходимо для распространения сведений об удалении среди всех синхронизируемых копий во избежание ошибочного воскрешения покойничков. Ведение такого журнала обязательно для каждой копии, форма записи зависит от вида хранилища данных. Такие дела.

Однако периодически сведения из этого журнала приходится удалять, во избежание проблем с производительностью и напрасной траты места. После этого сеансы промышленной некромантии становятся вполне вероятными, так что следует относиться к подобным чисткам логов с надлежащей осторожностью. Скажем об этом несколько подробнее.

Каждая копия содержит сведения только о собственном состоянии, и понятия не имеет о состоянии остальных. Определить, как широко распространились сведения о кончине того или иного элемента, невозможно. Поэтому как и когда очищать журнал удаленных элементов, остается полностью на совести разработчика. Например, можно устраивать регулярную, периодическую чистку, или ориентироваться по размеру журнала.

После чистки копия больше не помнит об удаленных элементах, но по-прежнему должна не допускать их воскрешения путем копирования из других копий. В этом ей помогают «забытые сведения», сохраняемые в хранилище метаданных.

Два основных сценария, использующих записи об удаленных элементах – синхронизация устаревшей копии и попытка обновления удаленного элемента.

Выявление устаревшей копии

В этом сценарии копия назначения очень давно не обновлялась, и ее требуется синхронизировать с копией-источником. Но копия-источник, идущая в ногу со временем, уже почистила лог и забыла обо всех удалениях. Ее провайдер ничего не может сказать по этому поводу провайдеру копии назначения. В полном соответствии с логикой процесса синхронизации провайдер копии назначения пришлет свои сведения провайдеру копии-источника и запросит изменения. В этот момент провайдер копии-источника должен сравнить полученные сведения с «забытыми сведениями» из хранилища метаданных. Если какой-либо элемент clock vector-а из «забытых сведений» больше соответствующего элемента clock vector-а из текущих сведений копии назначений, значит, копия-источник уже забыла о некоторых удалениях, о которых копия назначения еще не узнала.

Чтобы выявить такую ситуацию, копия сохраняет сведения о забытых записях. До внесения изменений MSF проверяет эти сведения. Если обнаруживается устаревшая версия, автоматически запускается полное перечисление исходных элементов для применения к копии назначения.

Если приложение зарегистрировало обработчик событий, Sync Framework даст приложению возможность продолжить или прервать полное обновление. В управляемом коде будет сгенерировано событие FullEnumerationNeeded, а в неуправляемом приложение получит вызов ISyncCallback::OnFullEnumerationNeeded.

Если никакого обработчика не создано, автоматически будет выполнено полное обновление.

Запрос обновления удаленного элемента

Предположим, что исходная копия назначения не синхронизировалась с тех пор, как копия назначения в последний раз очистила сведения об удаленных элементах. Элемент был удален из копии назначения, и запись о его удалении тоже уже удалена. Этот же элемент в копии-источнике был обновлен. Таким образом, элемент появляется в списке изменений, который провайдер копии-источника посылает в ответ на запрос синхронизации от провайдера копии назначения. Провайдер копии назначения должен распознать, что этот элемент – не новый, иначе элемент будет ошибочно восстановлен в копии назначения.

Текущую версию этого элемента в копии-источнике бесполезно сравнивать с текущими сведениями копии назначения – там об этом элементе ничего нет. Вместо этого со сведениями копии назначения нужно сравнивать версию создания элемента в копии-источнике, чтобы определить, что копия назначения когда-то знала об этом элементе. Поскольку удаление элемента заведомо произошло позже его создания, текущие сведения копии назначения должны содержать запись о создании элемента.

Таким образом, перед созданием нового элемента в копии назначения MSF сравнивает версию создания элемента со сведениями копии назначения. Если там есть версия создания элемента, значит, такой элемент когда-то существовал, но уже был убит и забыт.

Обязательные метаданные элементов копии

Каждому синхронизируемому элементу тоже должен соответствовать обязательный набор метаданных.

Глобальный ID

Идентификатор элемента копии. Поскольку за генерирование глобальных ID отвечает копия, она может использовать эти ID для повышения эффективности работы с данными. Например, можно определить, что глобальный ID – это GUID с 8-байтным префиксом, то есть:

typedef struct _SYNC_GID
{
  ULONGLONG ullGidPrefix;
  GUID guidUniqueId;
} 
SYNC_GID;

Такой префикс можно использовать при сортировке идентификаторов, что облегчит провайдерам перечисление изменение в копиях. В управляемом коде формат определяется в свойстве IdFormats, а в неуправляемом – в структуре ID_PARAMETERS.

Текущая версия

Самая поздняя версия конкретного элемента. Эта версия сохраняется при каждом единичном изменении элемента, к которому применимы единичные изменения. Например, у контакта могут быть разные номера версий для полей адреса и номера телефона. Версия – это 12-байтный структурированный логический номер версии, состоящий из ключа копии, в последний раз обновлявшей элемент, и значения счетчика тиков этой копии.

Версия создания

Номер версии, присвоенный данному элементу при создании.

Объем метаданных

Поскольку формат глобальных ID определяет копия, объем сохраняемых метаданных может существенно различаться. Однако при соблюдении рекомендованного формата, использующего GUID и 8-байтный префикс, на хранение обязательных метаданных элемента требуется 48 байт – 24 байта на глобальный ID, и по 12 байт на номер версии и версию создания элемента.

Ядро MSF

Ядро синхронизации MSF составляют сервисы для работы с метаданными, используемыми всеми компонентами MSF в процессе синхронизации. Эти сервисы предназначены для:

Эти компоненты освобождают приложения и провайдеры от необходимости вникать в детали работы с метаданными. Кроме того, ядро предоставляет средства работы с Metadata Storage Service, компонента, управляющего хранением метаданных.

Metadata Storage Service

Metadata Storage Service служит для сохранения метаданных синхронизации провайдерами, представляющими копии, неспособные делать это самостоятельно.

Metadata Storage Service API четко разграничивает хранилище данных и интерфейсы и методы, используемые для доступа к хранилищу данных, что позволяет реализовать и использовать альтернативные хранилища, практически не изменяя провайдер.

По умолчанию Metadata Storage Service использует для хранения метаданных Microsoft SQL Server Compact Edition. Он предоставляет методы для создания нового хранилища данных, работы с уже существующим хранилищем, инициализации метаданных копии, а также для явного управления транзакциями.

Интерфейсы Metadata Storage Service однопоточны. Вызовы могут делаться из любого потока, но многопоточные приложения должны обеспечивать синхронизацию потоков для использования этих интерфейсов.

Создание хранилища

Хранилище данных в управляемом коде представляется объектом SqlCeMetadataStore, а в неуправляемом – интерфейсом ISqlCESyncMetadataStore.

Для создания нового хранилища служит метод CreateStore.

Если создано новое хранилище, метаданные копии нужно инициализировать, прежде чем использовать. Для этого служит метод InitializeReplicaMetadata. Этот метод вызывается для каждой БД только один раз. Поэтому, если нужно определить пользовательские поля, о которых сказано ниже, это должно быть сделано именно в этот момент.

Данная реализация хранилища метаданных может хранить метаданные только одной копии.

Для доступа к метаданным используется метод GetReplicaMetadata, создающий объект, представляющий метаданные копии. Metadata Storage Service защищает БД от параллельных изменений, позволяя создавать только один такой объект для каждого ID копии.

Пользовательские поля

Разработчик может определить свои поля для метаданных элементов копии. Это делается при помощи объекта FieldSchema (или структуры CUSTOM_FIELD_DEFINITION в неуправляемом коде). Для таких полей указывается имя, тип и размер, а также индексируются они или нет. Как уже говорилось, пользовательские поля определяются при инициализации метаданных копии. Доступ к ним, как и к другим метаданным элемента, производится через объект ItemMetadata (интерфейс IItemMetadata в неуправляемом коде)

Транзакции

Транзакции могут выполняться явно или неявно. Обычно данные помещаются в БД следующим образом:

На этот момент данные еще не записаны на диск. Выбор момента записи определяется типом используемой транзакции.

Неявные транзакции

Если при сохранении значений свойств не была явно начата транзакция, используется неявная транзакция, при которой данные могут быть сохранены либо когда SQL Server Compact Edition сбросит на диск все изменения, либо когда будет успешно завершен процесс приложения, сохраняющего изменения. В случае сбоя или непредвиденного завершения приложения нет никакой гарантии, что данные будут сохранены – такую гарантию может дать только явная транзакция.

Явные транзакции

Явные транзакции запускаются вызовом метода BeginTransaction, а изменения сохраняются вызовом CommitTransaction. Как несложно догадаться, для отката транзакции служит метод RollbackTransaction.

Провайдеры синхронизации

В Microsoft Sync Framework провайдер синхронизации – это программный компонент, позволяющий синхронизировать между собой различные копии данных. В сущности, каждый провайдер представляет копию данных, например, БД или папку файловой системы.

Во время сессии синхронизации приложение, выполняющее синхронизацию, соединяет провайдера копии-источника данных с провайдером копии-приемника, и воспроизводит в приемнике изменения, накопившиеся в источнике. Например, провайдер для папки в файловой системе может быть связан с другим провайдером, отвечающим за аналогичную папку, для синхронизации файлов между этими папками. Для синхронизации приложение сначала создает объект сессии синхронизации, соединяет его с двумя объектами-провайдерами, после чего начинает сессию синхронизации.

В процессе синхронизации провайдер приемника данных сообщает о своем текущем состоянии, принимает список изменений от источника, выявляет конфликты между этим списком и текущим состоянием подведомственной ему копии и применяет изменения к своему хранилищу данных.

Провайдер источника данных использует информацию о состоянии приемника, чтобы выяснить, о каких изменениях в источнике еще не знает приемник, и отправляет список изменений провайдеру копии-приемника через метод GetChangeBatch.

В состав MSF входят следующие провайдеры:

Microsoft Sync Framework позволяет также создавать собственные провайдеры синхронизации, но реально входящие провайдеры покрывают большую часть распространенных сценариев. Ниже будет рассмотрено создание простейшего провайдера синхронизации.

Microsoft Sync Services для ADO.NET

Microsoft слегка лукавит, говоря, что MSF служит для синхронизации чего угодно с чем угодно. Форд в свое время говорил, что автомобиль может быть любого цвета, при условии, что он черный. Так и Sync Services для ADO.NET могут синхронизировать любого клиента с любым сервером, при условии, что этот клиент – SQL Server Compact 3.5 SP1. Провайдеров для других клиентских СУБД, насколько мне известно, пока не существует. Можно надеяться, что они появятся в ближайшем будущем, но пока что никаких их следов найти не удалось.

Впрочем, ничто не мешает разработчикам реализовать такие провайдеры самостоятельно. Пример создания провайдера синхронизации можно найти по адресу http://msdn2.microsoft.com/ru-ru/library/bb902809(en-us,SQL.100).aspx. Однако, поскольку в задачи этой статьи разработка провайдера для какой-либо СУБД не входит, рассмотрим работу Sync Services для ADO.NET на примере уже существующего провайдера.

Sync Services поддерживают двухуровневую и многоуровневую архитектуры синхронизации с любым сервером БД, для которого существует ADO.NET-провайдер. Для синхронизации клиентской БД с другими источниками данных используется сервис-ориентированная архитектура. Такой подход требует больше кода, чем двухуровневая или многоуровневая архитектура, но принципы синхронизации остаются теми же самыми.

Sync Services позволяют выполнять snapshot, download-only, upload-only и двунаправленную синхронизацию:

Архитектура Sync Services асимметрична. Это значит, что отслеживание изменений встроено в клиентскую БД, но на сервере это придется реализовать разработчику.

На приведенных ниже рисунках показаны компоненты, используемые в двухуровневых, многоуровневых и сервис-ориентированных архитектурах. На рисунках показан только один клиент. Реально с каждым сервером, как правило, синхронизируется много клиентов. Sync Services используют модель hub-and-spoke (насколько мы понимаем это ничем не отличается от топологии «звезда», впрочем, мы можем и ошибаться – прим.ред.).


Рисунок 2. Двухуровневая архитектура.

Схема двухуровневой архитектуры синхронизации приведена на рисунке 2.

Все элементы этой схемы, кроме, разумеется, двух БД, соответствуют классам Sync Services. Эти классы находятся в следующих DLL:

Все эти библиотеки зависят от System.dll и System.Data.dll из .NET Framework 2.0 или более поздних версий. Microsoft.Synchronization.Data.SqlServerCe.dll также зависит от System.Data.SqlServerCe.dll из SQL Server Compact 3.5 SP1. В двухуровневых приложениях все библиотеки находятся на клиенте. В многоуровневых приложениях Microsoft.Synchronization.Data.dll и Microsoft.Synchronization.Data.Server.dll лежат в отдельном слое, реализующем сервис синхронизации, и, возможно, расположенном на отдельном компьютере.


Рисунок 3. Многоуровневая архитектура.

На рисунке 3 приведена схема многоуровневой архитектуры синхронизации. В ней для связи клиентской и серверной БД используются прокси, сервис и транспортный механизм. Такая архитектура применяется чаще, чем двухуровневая, поскольку не требует прямого соединения между клиентом и сервером.


Рисунок 4. Сервис-ориентированная архитектура.

На рисунке 4 показана сервис-ориентированная архитектура. Она включает клиентскую БД, но серверной БД и соответствующих ей адаптеров и провайдеров синхронизации в этой схеме нет. Вместо них приложение должно использовать сервис и прокси, предоставляющие функциональность провайдера и адаптеров, например, получение списков изменений.

Элементам приведенных выше рисунков соответствуют классы SyncAgent, SqlCeClientSyncProvider, DbServerSyncProvider, SyncTable, SyncGroup и SyncAdapter.

Агент синхронизации (SyncAgent)

Агент синхронизации руководит синхронизацией. Обычно он находится на клиентской стороне и работает согласно запросам клиентского приложения. Агент традиционно для MSF работает с двумя провайдерами синхронизации.

Таблица синхронизации (SyncTable) и группа синхронизации (SyncGroup)

Таблица синхронизации определяется для каждой таблицы, синхронизируемую на клиенте. Она задает настройки, а также направление синхронизации: Snapshot, Download-only, Upload-only или двунаправленная.

После создания таблицы синхронизации она может быть введена в группу синхронизации. Группа синхронизации – это механизм, обеспечивающий согласованную синхронизацию набора таблиц.

Клиентский провайдер (SqlCeClientSyncProvider)

В состав MSF входит ClientSyncProvider, клиентский провайдер синхронизации для SQL Server CE 3.5. Других провайдеров, как уже говорилось, пока что в составе поставки нет, что печально.

Серверный провайдер (DbServerSyncProvider)

Серверный провайдер служит для связи с серверной БД и скрывает работу с СУБД от агента синхронизации.

В состав MSF входит серверный провайдер синхронизации DbServerSyncProvider, предназначенный для работы с реляционными СУБД, поддерживающими ADO.NET. Для каждой синхронизируемой таблицы создается объект SyncAdapter, описанный ниже, и содержащий команды для работы с БД. У провайдера также есть две собственные команды:

SyncAdapter

SyncAdapter создан по образу и подобию DataAdapter-а ADO.NET, но работает с другими командами. Смысл многих из них понятен без объяснений, для остальных же приведем краткие пояснения:

В SyncAdapter есть и другие команды, но далеко не факт, что все они вам понадобятся. Например, при реализации однонаправленного сценария потребуются только три SelectIncremental-команды. Больше того, для создания адаптеров «на лету» существует специальная приблуда SqlSyncAdapterBuilder.

SqlSyncAdapterBuilder служит для создания кода команд синхронизации, исполняемых серверным провайдером. Он порождает SQL-выражения на основе информации о таблицах, участвующих в синхронизации. Для работы ему нужно указать следующее:

SqlSyncAdapterBuilder использует эту информацию при создании адаптера синхронизации и команд на Transact-SQL для работы с SQL Server (версий 2000 и выше). Однако эффективность порождаемого им кода вызывает определенные сомнения – чтобы добиться хорошей производительности, придется все же вернуться к собственноручному созданию необходимых хранимых процедур.

Следующий код создает объект SyncAdapter для двунаправленной синхронизации таблицы Customer.

SqlSyncAdapterBuilder customerBuilder = new SqlSyncAdapterBuilder(serverConn);

customerBuilder.TableName = "Sales.Customer";
customerBuilder.TombstoneTableName = customerBuilder.TableName + "_Tombstone";
customerBuilder.SyncDirection = SyncDirection.Bidirectional;
customerBuilder.CreationTrackingColumn = "InsertTimestamp";
customerBuilder.UpdateTrackingColumn = "UpdateTimestamp";
customerBuilder.DeletionTrackingColumn = "DeleteTimestamp";
customerBuilder.CreationOriginatorIdColumn = "InsertId";
customerBuilder.UpdateOriginatorIdColumn = "UpdateId";
customerBuilder.DeletionOriginatorIdColumn = "DeleteId";

SyncAdapter customerSyncAdapter = customerBuilder.ToSyncAdapter();
customerSyncAdapter.TableName = "Customer";
this.SyncAdapters.Add(customerSyncAdapter);

Прокси, сервис и транспорт

Прокси, сервис и транспортный механизм используются в многоуровневой и сервис-ориентированной архитектурах. В многоуровневых приложениях используется Microsoft.Synchronization.Data.Server.dll, обычно располагающаяся в промежуточном слое. Прокси и сервис нужны, чтобы связать клиента с промежуточным слоем. Клиентский код приложения обращается к расположенному на клиенте прокси серверного провайдера синхронизации (ServerSyncProviderProxy), а не к самому провайдеру. Прокси связывается с сервисом, расположенным на промежуточном уровне, а сервис, в свою очередь, связывается с серверным провадером.

В сервис-ориентированных приложениях Microsoft.Synchronization.Data.Server.dll не используется. Код приложения должен предоставлять функциональность, которую обычно предоставляют адаптеры и серверный провайдер синхронизации.

Консольное приложение, использующее Sync Services для ADO.NET

Теперь, когда принципы работы Sync Services вам известны, попробуем создать простое консольное приложение, которое скачивает с сервера некий набор данных, а затем обновляет его. Это, конечно, очень примитивное приложение, зато в нем собран воедино код, разбросанный по всей документации MSF.

Чтобы приложение работало, нужно, чтобы на вашей машине были установлены Sync Services (приложение использует Microsoft.Synchronization.dll, Microsoft.Synchronization.Data.dll, Microsoft.Synchronization.Data.Server.dll и Microsoft.Synchronization.Data.SqlServerCe.dll), SQL Server Compact 3.5 SP1 и какой-нибудь еще SQL Server (например, SQL Server Express, поставляемый с Visual Studio), на котором будет лежать серверная БД. В строках подключения используется localhost. Если вы используете SQL Server Express, замените localhost на .\sqlexpress. В качестве БД используется база-пример, входящая в состав Sync Services, подробнее см. http://msdn2.microsoft.com/en-us/library/bb726041(SQL.100).aspx.

Приложение состоит из шести классов:

Прежде чем перейти непосредственно к коду, разберемся с основными действиями приложения.

Создание SyncTable

Следующий код создает объект SyncTable для таблицы Customer, указывает направление синхронизации и то, как таблица должна быть создана на клиенте. Если такая таблица уже есть, она будет пересоздана при первой синхронизации.

SyncTable customerSyncTable = new SyncTable("Customer");
customerSyncTable.CreationOption = 
  TableCreationOption.DropExistingOrCreateNewTable;
customerSyncTable.SyncDirection = SyncDirection.DownloadOnly;
this.Configuration.SyncTables.Add(customerSyncTable);

Использование SqlSyncAdapterBuilder

В данном случае адаптер синхронизации создается с помощью SqlSyncAdapterBuilder. Этот вопрос (и код) уже был разобран выше, поэтому здесь нет смысла останавливаться на этом.

Команда New Anchor

Следующий код служит для получения нового значения метки синхронизации с сервера. Это значение сохраняется в клиентской БД. При синхронизации значения, полученной при этой и при предыдущей синхронизации, служат верхней и нижней границами

В данном случае MIN_ACTIVE_ROWVERSION возвращает значение времени с сервера (MIN_ACTIVE_ROWVERSION появилась в SQL Server 2005 Service Pack 2). Время используется потому, что в колонках, указанных при использовании SqlSyncAdapterBuilder, используются значения времени. Если бы там булла указана дата, можно было бы использовать, например, GETUTCDATE().

Класс SyncSession содержит несколько строковых констант, которые можно использовать в командах синхронизации. Одна из них - SyncNewReceivedAnchor.

SqlCommand selectNewAnchorCommand = new SqlCommand();
string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
selectNewAnchorCommand.CommandText = 
  "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = 
  ParameterDirection.Output;
selectNewAnchorCommand.Connection = serverConn;
this.SelectNewAnchorCommand = selectNewAnchorCommand;

Вызов метода Synchronize

Следующий код создает экземпляр SampleSyncAgent и вызывает метод Synchronize. В классе SampleSyncAgent в качестве LocalProvider указан SampleClientSyncProvider, в качестве RemoteProvider – SampleServerSyncProvider. Кроме того, там указана описанная выше таблица синхронизации.

SampleSyncAgent sampleSyncAgent = new SampleSyncAgent();
SyncStatistics syncStatistics = sampleSyncAgent.Synchronize();

Класс SampleStats использует статистику, выдаваемую классом SyncAgent, чтобы информировать пользователя о ходе сессии синхронизации.

Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
Console.WriteLine("Total Changes Downloaded: " + syncStatistics.TotalChangesDownloaded);
Console.WriteLine("Complete Time: " + syncStatistics.SyncCompleteTime);
Console.WriteLine(String.Empty);

Полный код приложения

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.Server;
using Microsoft.Synchronization.Data.SqlServerCe;

namespace Microsoft.Samples.Synchronization
{
    class Program
    {
        static void Main(string[] args)
        {
            //The Utility class handles all functionality that is not
            //directly related to synchronization, such as holding connection 
            //string information and making changes to the server database.
            Utility util = new Utility();

            //The SampleStats class handles information from the SyncStatistics
            //object that the Synchronize method returns.
            SampleStats sampleStats = new SampleStats();

            //Delete and re-create the database. The client synchronization
            //provider also enables you to create the client database 
            //if it does not exist.
            util.SetClientPassword();
            util.RecreateClientDatabase();

            //Initial synchronization. Instantiate the SyncAgent
            //and call Synchronize.
            SampleSyncAgent sampleSyncAgent = new SampleSyncAgent();
            SyncStatistics syncStatistics = sampleSyncAgent.Synchronize();
            sampleStats.DisplayStats(syncStatistics, "initial");

            //Make changes on the server.
            util.MakeDataChangesOnServer();

            //Subsequent synchronization.
            syncStatistics = sampleSyncAgent.Synchronize();
            sampleStats.DisplayStats(syncStatistics, "subsequent");

            //Return server data back to its original state.
            util.CleanUpServer();

            //Exit.
            Console.Write("\nPress Enter to close the window.");
            Console.ReadLine();
        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.SyncAgent.
    public class SampleSyncAgent : SyncAgent
    {
        public SampleSyncAgent()
        {
            //Instantiate a client synchronization provider and specify it
            //as the local provider for this synchronization agent.
            this.LocalProvider = new SampleClientSyncProvider();

            //Instantiate a server synchronization provider and specify it
            //as the remote provider for this synchronization agent.
            this.RemoteProvider = new SampleServerSyncProvider();

            //Add the Customer table: specify a synchronization direction of
            //DownloadOnly.
            SyncTable customerSyncTable = new SyncTable("Customer");
            customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            customerSyncTable.SyncDirection = SyncDirection.DownloadOnly;
            this.Configuration.SyncTables.Add(customerSyncTable);
        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.Server.DbServerSyncProvider.
    public class SampleServerSyncProvider : DbServerSyncProvider
    {
        public SampleServerSyncProvider()
        {
            //Create a connection to the sample server database.
            Utility util = new Utility();
            SqlConnection serverConn = new SqlConnection(util.ServerConnString);
            this.Connection = serverConn;

            //Create a command to retrieve a new anchor value from
            //the server. In this case, we use a timestamp value
            //that is retrieved and stored in the client database.
            //During each synchronization, the new anchor value and
            //the last anchor value from the previous synchronization
            //are used: the set of changes between these upper and
            //lower bounds is synchronized.
            //
            //SyncSession.SyncNewReceivedAnchor is a string constant; 
            //you could also use @sync_new_received_anchor directly in 
            //your queries.
            SqlCommand selectNewAnchorCommand = new SqlCommand();
            string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
            selectNewAnchorCommand.CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
            selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
            selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Connection = serverConn;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;


            //Create a SyncAdapter for the Customer table by using 
            //the SqlSyncAdapterBuilder:
            //  * Specify the base table and tombstone table names.
            //  * Specify the columns that are used to track when
            //    changes are made.
            //  * Specify download-only synchronization.
            //  * Call ToSyncAdapter to create the SyncAdapter.
            //  * Specify a name for the SyncAdapter that matches the
            //    the name specified for the corresponding SyncTable.
            //    Do not include the schema names (Sales in this case).

            SqlSyncAdapterBuilder customerBuilder = new SqlSyncAdapterBuilder(serverConn);

            customerBuilder.TableName = "Sales.Customer";
            customerBuilder.TombstoneTableName = customerBuilder.TableName + "_Tombstone";
            customerBuilder.SyncDirection = SyncDirection.DownloadOnly;
            customerBuilder.CreationTrackingColumn = "InsertTimestamp";
            customerBuilder.UpdateTrackingColumn = "UpdateTimestamp";
            customerBuilder.DeletionTrackingColumn = "DeleteTimestamp";
            
            SyncAdapter customerSyncAdapter = customerBuilder.ToSyncAdapter();
            customerSyncAdapter.TableName = "Customer";
            this.SyncAdapters.Add(customerSyncAdapter);

        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider.
    //You can just instantiate the provider directly and associate it
    //with the SyncAgent, but you could use this class to handle client 
    //provider events and other client-side processing.
    public class SampleClientSyncProvider : SqlCeClientSyncProvider
    {

        public SampleClientSyncProvider()
        {
            //Specify a connection string for the sample client database.
            Utility util = new Utility();
            this.ConnectionString = util.ClientConnString;
        }
    }

    //Handle the statistics that are returned by the SyncAgent.
    public class SampleStats
    {
        public void DisplayStats(SyncStatistics 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 Downloaded: " + syncStatistics.TotalChangesDownloaded);
            Console.WriteLine("Complete Time: " + syncStatistics.SyncCompleteTime);
            Console.WriteLine(String.Empty);

        }
    }

    public class Utility
    {

        private static string _clientPassword;

        //Get and set the client database password.
        public static string Password
        {
            get { return _clientPassword; }
            set { _clientPassword = value; }
        }

        //Have the user enter a password for the client database file.
        public void SetClientPassword()
        {
            Console.WriteLine("Type a strong password for the client");
            Console.WriteLine("database, and then press Enter.");
            Utility.Password = Console.ReadLine();
        }

        //Return the client connection string with the password.
        public string ClientConnString
        {
            get { return @"Data Source='SyncSampleClient.sdf'; Password=" + Utility.Password; }
        }

        //Return the server connection string. 
        public string ServerConnString
        {

            get { return @"Data Source=localhost; Initial Catalog=SyncSamplesDb; Integrated Security=True"; }

        }

        //Make server changes that are synchronized on the second 
        //synchronization.
        public void MakeDataChangesOnServer()
        {
            int rowCount = 0;

            using (SqlConnection serverConn = new SqlConnection(this.ServerConnString))
            {
                SqlCommand sqlCommand = serverConn.CreateCommand();
                sqlCommand.CommandText =
                    "INSERT INTO Sales.Customer (CustomerName, SalesPerson, CustomerType) " +
                    "VALUES ('Cycle Mart', 'James Bailey', 'Retail') " +
                    
                    "UPDATE Sales.Customer " +
                    "SET  SalesPerson = 'James Bailey' " +
                    "WHERE CustomerName = 'Tandem Bicycle Store' " +

                    "DELETE FROM Sales.Customer WHERE CustomerName = 'Sharp Bikes'"; 
                
                serverConn.Open();
                rowCount = sqlCommand.ExecuteNonQuery();
                serverConn.Close();
            }

            Console.WriteLine("Rows inserted, updated, or deleted at the server: " + rowCount);
        }

        //Revert changes that were made during synchronization.
        public void CleanUpServer()
        {          
            using(SqlConnection serverConn = new SqlConnection(this.ServerConnString))
            {
                SqlCommand sqlCommand = serverConn.CreateCommand();
                sqlCommand.CommandType = CommandType.StoredProcedure;
                sqlCommand.CommandText = "usp_InsertSampleData";
                
                serverConn.Open();               
                sqlCommand.ExecuteNonQuery();
                serverConn.Close();
            }
        }

        //Delete the client database.
        public void RecreateClientDatabase()
        {
            using (SqlCeConnection clientConn = new SqlCeConnection(this.ClientConnString))
            {
                if (File.Exists(clientConn.Database))
                {
                    File.Delete(clientConn.Database);
                }
            }
                
            SqlCeEngine sqlCeEngine = new SqlCeEngine(this.ClientConnString);
            sqlCeEngine.CreateDatabase();
        }
    }

File System Provider

File System Provider (FS-провайдер) – это компонент для синхронизации файлов и каталогов между томами файловых систем NTFS или FAT. Он позволяет синхронизировать файловые данные (клиент/сервер, full-mesh, P2P), включая поддержку сменных носителей. Основные возможности FS-провайдера:

Использование FS-провайдера

Начнем с короткого куска кода, показывающего, как приложение может вызвать FS-провайдер. Этот C#-код использует .NET-классы-обертки для FS-провайдера, но на C++ все выглядело бы примерно так же.

public static void SyncFileSystemReplicasOneWay(
        SyncId sourceReplicaId, SyncId destinationReplicaId,
        string sourceReplicaRootPath, string destinationReplicaRootPath,
        FileSyncScopeFilter filter, FileSyncOptions options)
{
  using (var sourceProvider = FileSyncProvider(
    sourceReplicaId, sourceReplicaRootPath, filter, options))
  using (var destinationProvider = new FileSyncProvider(
    destinationReplicaId, destinationReplicaRootPath, filter, options))
  {
    destinationProvider.AppliedChange += OnAppliedChange;
    destinationProvider.SkippedChange += OnSkippedChange;

    SyncAgent agent = new SyncAgent();
    agent.LocalProvider = sourceProvider;
    agent.RemoteProvider = destinationProvider;
    agent.Direction = SyncDirection.Upload; // Синхронизировать директории

    Console .WriteLine( "Synchronizing changes to replica: " +
        destinationProvider.RootDirectoryPath);
    agent.Synchronize();
  }
}

Выявление изменений в файловой системе

FS-провайдер синхронизирует состояние различных объектов файловой системы. В терминах MSF эти объекты будут, конечно, копиями. Здесь уже говорилось, как в MSF организован процесс выявления изменений, и FS-провайдер ведет себя именно так. Поэтому ограничимся только специфичными для этого провайдера деталями.

При выявлении изменений файлов провайдер сравнивает размер, время создания и изменения, атрибуты, имя (с учетом регистра) и (необязательно) хэш-значение содержимого файла. В случае папок сравниваются атрибуты и имена папок.

В случае удаления или переименования файлов/папок выполняются такие же операции в копии назначения, что существенно экономит трафик.

Выявление изменений в случае больших томов файловой системы может быть довольно дорогостоящей операцией, поэтому провайдер допускает ее выполнение по запросу, а не при каждом начале сессии синхронизации (для явного запуска операции выявления изменений служит метод провайдера DetectChanges).

Индикатор прогресса и режим Preview

Для интерактивных приложений FS-провайдер поддерживает расширенное оповещение о ходе выполнения операций синхронизации. Это позволяет приложениям выводить пользователю индикатор прогресса и отображать текущее состояние операций синхронизаций.

Информация о ходе выполнения синхронизации сообщается приложению через callback-интерфейс, который приложение может предоставить провайдеру при инициализации. Поскольку обратные вызовы производятся перед каждым изменением в файловой системе (OnApplyingChange), приложение имеет возможность пропустить то или иное изменение.

FS-провайдер поддерживает синхронизацию в режиме Preview, что дает приложениям возможность отображать изменения, которые должны будут выполнены при синхронизации. В режиме предварительного просмотра изменения не применяются к файловой системе и метаданным, но большинство сообщений о состоянии синхронизации по-прежнему отсылается. Это можно использовать в приложении, чтобы предоставить пользователю интерфейс для просмотра и подтверждения всех изменений, вносимых при синхронизации.

Заметьте, что FS-провайдер не предоставляет оценки количества синхронизируемых файлов до начала синхронизации, поскольку выяснение этого количества может быть дорогостоящим при больших размерах реплицируемых томов файловой системы, и далеко не все приложения захотят платить такую цену. Однако приложение может, при желании, собрать такую статистику, используя двухпроходный подход: сперва запустить синхронизацию в режиме Preview и получить статистику изменений, а затем уже запустить реальную сессию.

Фильтрация файлов при синхронизации

Приложение, использующее FS-провайдер, может использовать фильтры для исключения файлов и папок из процесса синхронизации.

Тип фильтра По умолчанию Пример
Исключение по имени файла Исключений нетNo files excluded Не синхронизировать файлы, соответствующие маске, например, “*.mov,*.wmv” или “foo.txt”
Включение по имени файла Все файлы включены Синхронизировать только файлы, соответствующие маске, например, “*.doc, *.txt”, “special.jpg”.
Исключение подкаталогов Исключений нет Не синхронизировать подкаталоги, путь которых совпадает с указанным в списке, например, “c:\users\videos”.
Исключение по атрибуту файла Исключений нет Не синхронизировать файлы с атрибутами system или hidden.Do NOT synchronize system or hidden files

При использовании фильтров синхронизируются только файлы, проходящие все фильтры. Кроме того, как уже говорилось выше, приложение может сказать провайдеру пропустить некоторые файлы с помощью callback-метода OnApplyingChange. Наконец, системные файлы, используемые Windows Explorer, (например, desktop.ini или thumbs.db) всегда исключаются из синхронизации, при этом они могут быть не помечены атрибутами SYSTEM или HIDDEN.

Разрешение конфликтов

Иногда файлы и каталоги изменяются независимо на разных копиях файловой системы. При очередной синхронизации провайдер постарается разрешить возникающие конфликты, следуя определенным принципам:

В текущей версии FS-провайдер поддерживает только фиксированную политику автоматического разрешения конфликтов:

Конфликт «создание – создание». Если в двух копиях были независимо созданы два файла с одинаковыми именами, при синхронизации сохранится один файл с этим именем и с позже сохраненным содержимым. Если время сохранения совпадает, содержимое файлов считается одинаковым, и один из них выбирается на роль «победителя». Если провайдер поддерживает Recycle Bin, удаляемый файл перемещается туда и может быть восстановлен впоследствии.

Конфликт «создание – переименование». В этом случае при переименовании файла его имя начинает совпадать с именем уже существующего в другой копии файла. Решение состоит в переименовании одного из файлов.

Как видите, для корректного разрешения таких конфликтов нужно немало хитрозапутанной логики, особенно при синхронизации нескольких копий с произвольной топологией синхронизации. Радует то, что провайдер справляется с большинством таких ситуаций без вмешательства разработчика.

Обработка ошибок и восстановление после сбоев

FS-провайдер должен обрабатывать ошибки, возникающие при синхронизации наборов файлов. В случае недоступности файла, нехватки дискового пространства и других ошибок провайдер пропускает эти файлы и занимается синхронизацией остальных.

Для каждого неудачного изменения файла в копии назначения приложению передается информация об ошибке. В некоторых случаях синхронизирующее приложение может попытаться исправить ошибку самостоятельно, в других принятие решение может быть предоставлено пользователю.

Возможны также ошибки синхронизации, приводящие к невозможности синхронизации как таковой. В этом случае метод Synchronize возвращает соответствующий код ошибки, на который приложение может реагировать соответствующим образом. Например, параллельные операции синхронизации для одной копии в текущей версии невозможны, и при попытке сделать это возникает ошибка “replica in use”. Приложение может реагировать на эту ошибку, отложив синхронизацию до лучших времен.

Пример кода – использование FileSyncProvider

using System;
using System.IO;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Files;

public class FileSyncProviderSample
{
  public static void Main(string[] args)
  {
    if (args.Length < 2 || string.IsNullOrEmpty(args[0]) 
      || string.IsNullOrEmpty(args[1]) || !Directory.Exists(args[0]) 
      || !Directory.Exists(args[1]))
    {
      Console.WriteLine("Usage: FileSyncSample [valid directory path 1] "
        + "[valid directory path 2]");
      return;
    }

    string replica1RootPath = args[0];
    string replica2RootPath = args[1];
    string idFileName = "filesync.id";

    SyncId replica1Id = GetReplicaId(Path.Combine(args[0], idFileName));
    SyncId replica2Id = GetReplicaId(Path.Combine(args[1], idFileName));

    try
    {
      // Задание настроек
      var options = FileSyncOptions.ExplicitDetectChanges 
        | FileSyncOptions.RecycleDeletes | FileSyncOptions.RecycleOverwrites;

      var filter = new FileSyncScopeFilter();
      filter.FileNameExcludes.Add(idFileName); // Исключить id-файлы

      // Явное определение изменений на обеих копиях, 
      // чтобы избежать двух проходов при определении изменений 
      // в случае двунаправленной синхронизации
      DetectChangesOnFileSystemReplica(
        replica1Id, replica1RootPath, filter, options);

      DetectChangesOnFileSystemReplica(
        replica2Id, replica2RootPath, filter, options);

      // Двунаправленная синхронизация
      SyncFileSystemReplicasOneWay(replica1Id, replica2Id,
        replica1RootPath, replica2RootPath, filter, options);
      SyncFileSystemReplicasOneWay(replica2Id, replica1Id,
        replica2RootPath, replica1RootPath, filter, options);
    }
    catch (Exception e)
    {
      Console.WriteLine("\nException from File System Provider:\n" + e);
    }
  }

  public static void DetectChangesOnFileSystemReplica(
      SyncId replicaId, string replicaRootPath,
      FileSyncScopeFilter filter, FileSyncOptions options)
  {
    FileSyncProvider provider = null;

    try
    {
      provider =
        new FileSyncProvider(replicaId, replicaRootPath, filter, options);
      provider.DetectChanges();
    }
    finally
    {
      // Освобождение ресурсов
      if (provider != null)
        provider.Dispose();
    }
  }

public static void SyncFileSystemReplicasOneWay(
        SyncId sourceReplicaId, SyncId destinationReplicaId,
        string sourceReplicaRootPath, string destinationReplicaRootPath,
        FileSyncScopeFilter filter, FileSyncOptions options)
{
  using (var sourceProvider = FileSyncProvider(
    sourceReplicaId, sourceReplicaRootPath, filter, options))
  using (var destinationProvider = new FileSyncProvider(
    destinationReplicaId, destinationReplicaRootPath, filter, options))
  {
    destinationProvider.AppliedChange += OnAppliedChange;
    destinationProvider.SkippedChange += OnSkippedChange;

    SyncAgent agent = new SyncAgent();
    agent.LocalProvider = sourceProvider;
    agent.RemoteProvider = destinationProvider;
    agent.Direction = SyncDirection.Upload; // Синхронизировать директории

    Console .WriteLine( "Synchronizing changes to replica: " +
        destinationProvider.RootDirectoryPath);
    agent.Synchronize();
  }
}

  public static void OnAppliedChange(object sender, AppliedChangeEventArgs args)
  {
    switch (args.ChangeType)
    {
      case ChangeType.Create:
        Console.WriteLine("-- Applied CREATE for file " + args.NewFilePath);
        break;
      case ChangeType.Delete:
        Console.WriteLine("-- Applied DELETE for file " + args.OldFilePath);
        break;
      case ChangeType.Overwrite:
        Console.WriteLine("-- Applied OVERWRITE for file " + args.OldFilePath);
        break;
      case ChangeType.Rename:
        Console.WriteLine("-- Applied RENAME for file " + args.OldFilePath +
                  " as " + args.NewFilePath);
        break;
    }
  }

  public static void OnSkippedChange(object sender, SkippedChangeEventArgs args)
  {
    Console.WriteLine("-- Skipped applying " + args.ChangeType.ToString().ToUpper()
        + " for " + (!string.IsNullOrEmpty(args.CurrentFilePath) ?
              args.CurrentFilePath : args.NewFilePath) + " due to error");

    if (args.Exception != null)
      Console.WriteLine("   [" + args.Exception.Message + "]");
  }

  public static SyncId GetReplicaId(string idFilePath)
  {
    SyncId replicaId = null;

    if (File.Exists(idFilePath))
    {
      using (StreamReader sr = File.OpenText(idFilePath))
      {
        string strGuid = sr.ReadLine();
        if (!string.IsNullOrEmpty(strGuid))
          replicaId = new SyncId(new Guid(strGuid));
      }
    }

    if (replicaId == null)
    {
      using (FileStream idFile = File.Open(
            idFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
      {
        using (StreamWriter sw = new StreamWriter(idFile))
        {
          replicaId = new SyncId(Guid.NewGuid());
          sw.WriteLine(replicaId.GetGuidId().ToString("D"));
        }
      }
    }

    return replicaId;
  }
}

Sync Services for FeedSync

Sync Services for FeedSync предоставляют клиентский и серверный сервисы, которые можно использовать для синхронизации данных копии с RSS- и Atom-лентами. Используя серверный FeedSync-сервис, приложение может работать с провайдером синхронизации для создания списка элементов копии и размещения их в RSS или Atom XML-потоке данных. Затем эти элементы могут быть разосланы подписчикам. Клиентский FeedSync-сервис позволяет приложению использовать RSS или Atom XML-поток данных как источник данных, выбирать из него элементы и затем использовать провайдер синхронизации для применения изменений к своей копии данных. Это позволяет синхронизировать между собой, например, две RSS- или Atom-ленты.

Заключение

Microsoft Sync Framework позволяет создавать решения, реализующие самые разные сценарии синхронизации. На сегодняшний день это одно из немногих, а может, и единственное бесплатное решение с открытой архитектурой и широким набором возможностей. Сейчас, когда все большую популярность приобретают мобильные устройства, и, следовательно, отсоединенные режимы работы, задача синхронизации данных приобрела гораздо большее значение, чем это было раньше. Несомненно, появление Microsoft Sync Framework упростит разработку надежных приложений, работающих в отсоединенном режиме, предоставив разработчикам надежную и оптимизированную среду для создания синхронизационных решений с поддержкой кэширования, совместного доступа, различных протоколов и источников данных.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.

Copyright © 1994-2016 ООО "К-Пресс"