Как доставить изменения в пакетах (SQL Server)
В этом разделе описывается, как выполнять пакетную передачу изменений для синхронизации в платформе Sync Framework баз данных с помощью поставщиков SqlSyncProvider, SqlCeSyncProvider или DbSyncProvider. Код из этого раздела построен на следующих классах Sync Framework:
Основные сведения о пакетной передаче
По умолчанию платформа Sync Framework передает изменения на каждый узел в виде отдельного объекта DataSet. Пока изменения применяются к узлу, этот объект хранится в памяти. Такое поведение по умолчанию вполне применимо, если компьютер, на котором применяются изменения, располагает достаточным объемом памяти, а соединение надежное. Однако для некоторых приложений удобнее, если изменения разделены на пакеты. Рассмотрите следующий сценарий для приложения синхронизации.
Большое количество клиентов, использующих SqlCeSyncProvider, периодически синхронизируются с сервером, использующим SqlSyncProvider.
Объем памяти и место на диске ограничены на каждом клиенте.
Соединения между сервером и клиентами имеют низкую пропускную способность и подвержены перебоям, что часто затягивает процесс синхронизации и приводит к потере соединений.
Изменения, передаваемые в ходе типичного сеанса синхронизации, имеют большой размер (в КБ).
Для сценариев такого типа идеально подходит пакетная передача изменений, обладающая следующими возможностями.
Позволяет разработчику управлять объемом памяти (размера кэша данных в памяти), используемой для хранения изменений на клиенте. Это позволяет избежать возникновения ошибок нехватки памяти на клиенте.
Позволяет Sync Framework перезапускать неуспешно завершившуюся операцию синхронизации с начала текущего пакета, а не с начала всего набора изменений.
Может снизить или устранить потребность в повторной загрузке или повторном перечислении изменений на сервере в результате ошибочных операций.
Пакетную передачу просто настроить для двухуровневых и многоуровневых приложений. Ее можно использовать как для исходного сеанса синхронизации, так и для последующих сеансов.
Настройка и использование пакетной передачи
Пакетная передача в платформе Sync Framework работает следующим образом.
Приложение указывает размер кэша данных в памяти для каждого поставщика, участвующего в сеансе синхронизации.
Если оба поставщика указывают размер кэша, то Sync Framework использует для обоих поставщиков меньшее значение из указанных. Фактический размер кэша не будет превышать 110 % от меньшего из указанных размеров. Если в ходе сеанса синхронизации размер одной строки превысит 100 % от размера кэша, сеанс будет завершен с вызовом исключения.
Значение 0 (по умолчанию) отключает пакетную передачу. Если пакетная передача включена только в одном поставщике, то пакетная передача выполняется и для отправки, и для загрузки.
Приложение указывает расположение файлов буферизации для каждого поставщика. По умолчанию файлы буферизации записываются во временный каталог учетной записи, с которой работает процесс синхронизации.
Приложение вызывает метод Synchronize.
Sync Framework перечисляет изменения по одной строке. Если достигнут предельный размер кэша данных в памяти для поставщика источника, то изменения сохраняются в локальном файле буферизации, а данные записываются из памяти на диск. Этот процесс продолжается до завершения перечисления всех изменений.
В многоуровневых сценариях код службы и учетная запись-посредник в приложении направляет файлы буферизации в назначение. Дополнительные сведения см. в подразделе Код, относящийся к многоуровневым сценариям в этом разделе. В двухуровневых сценариях локальный файл уже находится в назначении, поскольку в этом случае весь код синхронизации выполняется в назначении.
Sync Framework выполняет десериализацию изменений из файлов буферизации и применяет эти изменения. Этот процесс продолжается до завершения применения всех изменений в назначении.
Все пакеты применяются в рамках одной транзакции. Транзакция не создается, пока последний пакет не получен поставщиком назначения.
В двухуровневых сценариях платформа Sync Framework очищает файл буферизации. В многоуровневых сценариях платформа Sync Framework очищает файлы буферизации на компьютере, где запущена синхронизация, однако файлы на промежуточном уровне должны очищаться учетной записью-посредником (как показано в образце метода Cleanup() далее в этом разделе). Для обработки ситуаций, в которых работа сеанса прерывается, на среднем уровне должен применяться процесс очистки файлов старше определенного срока.
Примечание
Изменения данных, применяемые к узлу, доступны через свойство Context объекта DbChangesSelectedEventArgs. Если данные не имеют пакетной структуры, то событие ChangesSelected вызывается только один раз и все изменения доступны через свойство Context. Если данные имеют пакетную структуру, то событие ChangesSelected вызывается для каждого пакета и во время этого вызова доступны только изменения из этого пакета. Если необходимы изменения из всех пакетов, то обработайте все события ChangesSelected, сохранив возвращаемые данные.
В следующей таблице описаны типы и элементы, относящиеся к пакетной передаче данных. Для пакетирования является обязательным только свойство MemoryDataCacheSize, однако рекомендуется настроить и свойство BatchingDirectory.
Тип или элемент |
Описание |
---|---|
Возвращает или задает каталог, в котором сохраняются пакетные файлы, сохраняемые на диске. Указанный путь должен быть локальным каталогом выполняющегося поставщика или учетной записью-посредником. Пути к файлам в формате UNC и пути URI к объектам, не являющимся файлами, не поддерживаются. Внимание! Файлы буферизации содержат необработанные данные из базы данных. Каталог, в который записываются файлы, необходимо защитить с помощью средств контроля доступа. | |
Возвращает или задает значение, определяющее, следует ли выполнять очистку пакетных файлов после применения содержащихся в них изменений к объектам назначения. По умолчанию файлы очищаются. |
|
Возвращает или задает максимальный объем памяти (в КБ), используемой Sync Framework для кэширования изменений перед сохранением на диске. Примечание Этот параметр влияет только на размер данных и метаданных, хранящихся в памяти для изменений, отправленных в назначение. Он не ограничивает объем памяти, используемой компонентами Sync Framework и компонентами пользовательских приложений. | |
Событие, вызываемое после применения каждого пакета изменений в назначении. |
|
Событие, вызываемое после записи каждого пакета изменений на диск. |
|
Предоставляет данные для события BatchApplied, включая номер текущего пакета и общее количество применяемых пакетов. |
|
Предоставляет данные для события BatchSpooled, включая номер текущего пакета и размер пакета. |
|
Возвращает или задает имя файла, в который будут записаны буферизованные изменения. |
|
Возвращает или задает значение, определяющее способ отправки данных — несколькими пакетами или единым объектом DataSet. |
|
Возвращает или задает значение, определяющее, является ли текущий пакет изменений последним. |
|
Возвращает или задает количество операций удаления, выполненных повторно за время сеанса синхронизации с пакетной обработкой изменений. Повторное выполнение удалений в пакетах может быть вызвано порядком удаления значений первичного и внешнего ключей. Если удаление внешнего ключа отсутствует в текущем или более раннем пакете, то соответствующее удаление первичного ключа завершится ошибкой. После применения всех пакетов неуспешно завершившиеся операции удаления будут повторены. |
|
SelectIncrementalChangesCommand (применимо только для DbSyncProvider) |
Возвращает или задает запрос или хранимую процедуру для выборки добавочных изменений в локальной базе данных. Примечание Рекомендуется включать в указываемый запрос предложение ORDER BY [sync_row_timestamp]. Сортировка строк по значению временной метки гарантирует, что в случае перезапуска сеанса синхронизации поставщик начнет перечисление с максимальной отметки времени (в каждом пакете сохраняются служебные отметки для каждой таблицы) и изменения не будут потеряны. |
Возвращает или задает объект DataTable, содержащий изменения, подлежащие синхронизации. Если включена пакетная передача, то при доступе к этому свойству выполняется десериализация файла буферизации с диска. Затем все изменения, внесенные в таблицы, снова сохраняются в файл буферизации. |
|
Возвращает или задает объект DataSet, который содержит строки из одноранговой базы данных. Возвращает значение NULL, если свойство IsDataBatched имеет значение true. |
Общий код для двухуровневых и многоуровневых сценариев
В примерах кода из этого раздела показано, как обрабатывать пакетную передачу в двухуровневых и многоуровневых сценариях. Этот код взят из двух образцов, входящих в пакет SDK платформы Sync Framework: SharingAppDemo-CEProviderEndToEnd и WebSharingAppDemo-CEProviderEndToEnd. В начале каждого примера указывается расположение кода, например SharingAppDemo/CESharingForm. С точки зрения пакетной передачи, главным различием между этими двумя приложениями является дополнительный код, необходимый в многоуровневом сценарии для отправки и загрузки файлов буферизации и создания каталогов для каждого узла, выполняющего перечисление изменений.
В следующем примере кода из обработчика события synchronizeBtn_Click в SharingAppDemo/CESharingForm задается размер кэша данных в памяти и каталог, в который будут записываться файлы буферизации. Путь, указываемый в BatchingDirectory, должен быть локальным каталогом выполняющегося поставщика или учетной записью-посредником. Пути к файлам в формате UNC и пути URI к объектам, не являющимся файлами, не поддерживаются. Путь, указанный для BatchingDirectory, является корневым каталогом. Для каждого сеанса синхронизации платформа Sync Framework создает уникальный вложенный каталог, в котором хранятся файлы буферизации для этого сеанса. Этот каталог является уникальным для текущего сочетания источника и назначения, что позволяет изолировать файлы, используемые в различных сеансах. При выборе каталога пакетной обработки необходимо учитывать возможные побочные эффекты. Например, если поставщик размещен в службах Internet Information Services (IIS), не следует в качестве каталога пакетной обработки использовать виртуальный каталог IIS. Изменение элементов в виртуальном каталоге может привести к перезапуску IIS, что приведет к ошибке синхронизации.
В следующем примере кода из обработчика события synchronizeBtn_Click в WebSharingAppDemo/CESharingForm задаются те же свойства, однако каталог пакетной передачи для адреса назначения задается для прокси-поставщика, а не сразу для поставщика, как в двухуровневом сценарии:
//Set memory data cache size property. 0 represents non batched mode.
//No need to set memory cache size for Proxy, because the source is
//enabled for batching: both upload and download will be batched.
srcProvider.MemoryDataCacheSize = this._batchSize;
//Set batch spool location. Default value if not set is %Temp% directory.
if (!string.IsNullOrEmpty(this.batchSpoolLocation.Text))
{
srcProvider.BatchingDirectory = this.batchSpoolLocation.Text;
destinationProxy.BatchingDirectory = this.batchSpoolLocation.Text;
}
В следующих примерах кода из файла SynchronizationHelper в обоих приложениях создаются методы для обработки событий BatchSpooled и BatchAppliedEvents, которые вызываются поставщиком в ходе перечисления и применения изменений:
void provider_BatchSpooled(object sender, DbBatchSpooledEventArgs e)
{
this.progressForm.listSyncProgress.Items.Add("BatchSpooled event fired: Details");
this.progressForm.listSyncProgress.Items.Add("\tSource Database :" + ((RelationalSyncProvider)sender).Connection.Database);
this.progressForm.listSyncProgress.Items.Add("\tBatch Name :" + e.BatchFileName);
this.progressForm.listSyncProgress.Items.Add("\tBatch Size :" + e.DataCacheSize);
this.progressForm.listSyncProgress.Items.Add("\tBatch Number :" + e.CurrentBatchNumber);
this.progressForm.listSyncProgress.Items.Add("\tTotal Batches :" + e.TotalBatchesSpooled);
this.progressForm.listSyncProgress.Items.Add("\tBatch Watermark :" + ReadTableWatermarks(e.CurrentBatchTableWatermarks));
}
void provider_BatchApplied(object sender, DbBatchAppliedEventArgs e)
{
this.progressForm.listSyncProgress.Items.Add("BatchApplied event fired: Details");
this.progressForm.listSyncProgress.Items.Add("\tDestination Database :" + ((RelationalSyncProvider)sender).Connection.Database);
this.progressForm.listSyncProgress.Items.Add("\tBatch Number :" + e.CurrentBatchNumber);
this.progressForm.listSyncProgress.Items.Add("\tTotal Batches To Apply :" + e.TotalBatchesToApply);
}
//Reads the watermarks for each table from the batch spooled event. //The watermark denotes the max tickcount for each table in each batch.
private string ReadTableWatermarks(Dictionary<string, ulong> dictionary)
{
StringBuilder builder = new StringBuilder();
Dictionary<string, ulong> dictionaryClone = new Dictionary<string, ulong>(dictionary);
foreach (KeyValuePair<string, ulong> kvp in dictionaryClone)
{
builder.Append(kvp.Key).Append(":").Append(kvp.Value).Append(",");
}
return builder.ToString();
}
Код, относящийся к многоуровневым сценариям
Остальные примеры кода относятся только к многоуровневому сценарию в WebSharingAppDemo. Код, относящийся к многоуровневым сценариям, содержится в следующих трех файлах.
Контракт службы: IRelationalSyncContract
Веб-служба: RelationalWebSyncService
Учетная запись-посредник: RelationalProviderProxy
Оба поставщика, SqlSyncProvider и SqlCeSyncProvider, наследуют класс RelationalSyncProvider, и поэтому данный код применяется к обоим поставщикам. Дополнительные функции, связанные с хранилищем, разделяются на файлы учетной записи-посредника и службы для каждого типа поставщика.
Чтобы показать работу пакетной обработки в многоуровневом сценарии, рассмотрим сеанс синхронизации, где сервер выступает в роли источника, а клиент — в роли назначения. После записи изменений в локальный каталог на сервере для загруженных изменений выполняется следующий процесс.
На посреднике клиента вызывается метод GetChangeBatch. Как показано далее в образце кода, этот метод должен содержать специальный код для обработки пакетной передачи.
Служба получает файл пакета от поставщика SqlSyncProvider. Служба удаляет полный путь и отправляет по сети только имя файла. Это предотвращает передачу сведений о серверной структуре каталогов на клиенты.
Вызов посредником метода GetChangeBatch завершает работу.
Посредник обнаруживает, что изменения организованы в пакеты, и вызывает метод DownloadBatchFile, передавая имя файла пакета в качестве аргумента.
Посредник создает уникальный каталог (если для сеанса еще не существует каталог) в каталоге RelationalProviderProxy.BatchingDirectory для локального хранения этих файлов пакетов. Именем каталога служит идентификатор реплики однорангового узла, который выполняет перечисление изменений. Это гарантирует наличие в службе и учетной записи-посреднике уникального каталога для каждого однорангового узла, выполняющего перечисление.
Учетная запись-посредник загружает файл и сохраняет его локально. Учетная запись-посредник заменяет имя файла в контексте на новый полный путь к файлу пакета на локальном диске.
Учетная запись-посредник возвращает контекст в модуль взаимодействия.
Повторяйте шаги с 1 по 6, пока учетная запись-посредник не получит последний пакет.
Для переданных изменений выполняется следующий процесс.
Модуль взаимодействия вызывает ProcessChangeBatch на учетной записи-посреднике.
Учетная запись-посредник определяет, что файл относится к пакету, и выполняет следующие шаги.
Удаляет полный путь и отправляет по сети только имя файла.
Вызывает метод HasUploadedBatchFile, чтобы определить, был ли файл передан ранее. В таком случае шаг C выполнять необязательно.
Если метод HasUploadedBatchFile возвращает значение false, вызывается метод UploadBatchFile для службы и передает содержимое файла пакета.
Служба получает вызов UploadBatchFile и сохраняет пакет локально. Создание каталога выполняет аналогично шагу 4, указанному выше.
Вызывает метод ApplyChanges в службе.
Сервер получает вызов ApplyChanges и определяет, что файл относится к пакету. Сервер заменяет имя файла в контексте на новый полный путь к файлу пакета на локальном диске.
Сервер передает объект DbSyncContext локальному поставщику SqlSyncProvider.
Повторяйте шаги с 1 по 6, пока не будет отправлен последний пакет.
В следующем примере кода из IRelationalSyncContract указываются методы отправки и загрузки, которые используются для передачи файлов буферизации на средний уровень и обратно:
[OperationContract(IsOneWay = true)]
void UploadBatchFile(string batchFileid, byte[] batchFile);
[OperationContract]
byte[] DownloadBatchFile(string batchFileId);
В следующих примерах кода из RelationalWebSyncService доступны методы UploadBatchFile и DownloadBatchFile, определенные в контракте, и приводится дополнительная логика обработки пакетной передачи в следующих методах:
Cleanup: очищает все файлы буферизации из указанного каталога (или временного каталога, если каталог не указан).
GetChanges: проверяет, имеют ли данные пакетную структуру, и в таком случае удаляет путь к каталогу файла буферизации, чтобы путь не отправлялся по сети. В многоуровневых сценариях отправка полных путей к каталогам по сетевому подключению представляет угрозу безопасности. Именем файла является идентификатор GUID.
HasUploadedBatchFile: возвращает значение, показывающее, был ли указанный файл пакета отправлен в службу.
ApplyChanges: проверяет, имеют ли данные пакетную структуру, и в таком случае проверяет, был ли передан ожидаемый файл пакета. Если файл еще не передан, вызывается исключение. Клиент должен передать файл буферизации перед вызовом метода ApplyChanges.
public abstract class RelationalWebSyncService: IRelationalSyncContract
{
protected bool isProxyToCompactDatabase;
protected RelationalSyncProvider peerProvider;
protected DirectoryInfo sessionBatchingDirectory = null;
protected Dictionary<string, string> batchIdToFileMapper;
int batchCount = 0;
public void Initialize(string scopeName, string hostName)
{
this.peerProvider = this.ConfigureProvider(scopeName, hostName);
this.batchIdToFileMapper = new Dictionary<string, string>();
}
public void Cleanup()
{
this.peerProvider = null;
//Delete all file in the temp session directory
if (sessionBatchingDirectory != null && sessionBatchingDirectory.Exists)
{
try
{
sessionBatchingDirectory.Delete(true);
}
catch
{
//Ignore
}
}
}
public void BeginSession(SyncProviderPosition position)
{
Log("*****************************************************************");
Log("******************** New Sync Session ***************************");
Log("*****************************************************************");
Log("BeginSession: ScopeName: {0}, Position: {1}", this.peerProvider.ScopeName, position);
//Clean the mapper for each session.
this.batchIdToFileMapper = new Dictionary<string, string>();
this.peerProvider.BeginSession(position, null/*SyncSessionContext*/);
this.batchCount = 0;
}
public SyncBatchParameters GetKnowledge()
{
Log("GetSyncBatchParameters: {0}", this.peerProvider.Connection.ConnectionString);
SyncBatchParameters destParameters = new SyncBatchParameters();
this.peerProvider.GetSyncBatchParameters(out destParameters.BatchSize, out destParameters.DestinationKnowledge);
return destParameters;
}
public GetChangesParameters GetChanges(uint batchSize, SyncKnowledge destinationKnowledge)
{
Log("GetChangeBatch: {0}", this.peerProvider.Connection.ConnectionString);
GetChangesParameters changesWrapper = new GetChangesParameters();
changesWrapper.ChangeBatch = this.peerProvider.GetChangeBatch(batchSize, destinationKnowledge, out changesWrapper.DataRetriever);
DbSyncContext context = changesWrapper.DataRetriever as DbSyncContext;
//Check to see if data is batched
if (context != null && context.IsDataBatched)
{
Log("GetChangeBatch: Data Batched. Current Batch #:{0}", ++this.batchCount);
//Dont send the file location info. Just send the file name
string fileName = new FileInfo(context.BatchFileName).Name;
this.batchIdToFileMapper[fileName] = context.BatchFileName;
context.BatchFileName = fileName;
}
return changesWrapper;
}
public SyncSessionStatistics ApplyChanges(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeData)
{
Log("ProcessChangeBatch: {0}", this.peerProvider.Connection.ConnectionString);
DbSyncContext dataRetriever = changeData as DbSyncContext;
if (dataRetriever != null && dataRetriever.IsDataBatched)
{
string remotePeerId = dataRetriever.MadeWithKnowledge.ReplicaId.ToString();
//Data is batched. The client should have uploaded this file to us prior to calling ApplyChanges.
//So look for it.
//The Id would be the DbSyncContext.BatchFileName which is just the batch file name without the complete path
string localBatchFileName = null;
if (!this.batchIdToFileMapper.TryGetValue(dataRetriever.BatchFileName, out localBatchFileName))
{
//Service has not received this file. Throw exception
throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("No batch file uploaded for id " + dataRetriever.BatchFileName, null));
}
dataRetriever.BatchFileName = localBatchFileName;
}
SyncSessionStatistics sessionStatistics = new SyncSessionStatistics();
this.peerProvider.ProcessChangeBatch(resolutionPolicy, sourceChanges, changeData, new SyncCallbacks(), sessionStatistics);
return sessionStatistics;
}
public void EndSession()
{
Log("EndSession: {0}", this.peerProvider.Connection.ConnectionString);
Log("*****************************************************************");
Log("******************** End Sync Session ***************************");
Log("*****************************************************************");
this.peerProvider.EndSession(null);
Log("");
}
/// <summary>
/// Used by proxy to see if the batch file has already been uploaded. Optimizes by not resending batch files.
/// NOTE: This method takes in a file name as an input parameter and hence is suseptible for name canonicalization
/// attacks. This sample is meant to be a starting point in demonstrating how to transfer sync batch files and is
/// not intended to be a secure way of doing the same. This SHOULD NOT be used as such in production environment
/// without doing proper security analysis.
///
/// Please refer to the following two MSDN whitepapers for more information on guidelines for securing Web servies.
///
/// Design Guidelines for Secure Web Applications - https://msdn.microsoft.com/en-us/library/aa302420.aspx (Refer InputValidation section)
/// Architecture and Design Review for Security - https://msdn.microsoft.com/en-us/library/aa302421.aspx (Refer InputValidation section)
/// </summary>
/// <param name="batchFileId"></param>
/// <returns>bool</returns>
public bool HasUploadedBatchFile(String batchFileId, string remotePeerId)
{
this.CheckAndCreateBatchingDirectory(remotePeerId);
//The batchFileId is the fileName without the path information in it.
FileInfo fileInfo = new FileInfo(Path.Combine(this.sessionBatchingDirectory.FullName, batchFileId));
if (fileInfo.Exists && !this.batchIdToFileMapper.ContainsKey(batchFileId))
{
//If file exists but is not in the memory id to location mapper then add it to the mapping
this.batchIdToFileMapper.Add(batchFileId, fileInfo.FullName);
}
//Check to see if the proxy has already uploaded this file to the service
return fileInfo.Exists;
}
/// <summary>
/// NOTE: This method takes in a file name as an input parameter and hence is suseptible for name canonicalization
/// attacks. This sample is meant to be a starting point in demonstrating how to transfer sync batch files and is
/// not intended to be a secure way of doing the same. This SHOULD NOT be used as such in production environment
/// without doing proper security analysis.
///
/// Please refer to the following two MSDN whitepapers for more information on guidelines for securing Web servies.
///
/// Design Guidelines for Secure Web Applications - https://msdn.microsoft.com/en-us/library/aa302420.aspx (Refer InputValidation section)
/// Architecture and Design Review for Security - https://msdn.microsoft.com/en-us/library/aa302421.aspx (Refer InputValidation section)
/// </summary>
/// <param name="batchFileId"></param>
/// <param name="batchContents"></param>
/// <param name="remotePeerId"></param>
public void UploadBatchFile(string batchFileId, byte[] batchContents, string remotePeerId)
{
Log("UploadBatchFile: {0}", this.peerProvider.Connection.ConnectionString);
try
{
if (HasUploadedBatchFile(batchFileId, remotePeerId))
{
//Service has already received this file. So dont save it again.
return;
}
//Service hasnt seen the file yet so save it.
String localFileLocation = Path.Combine(sessionBatchingDirectory.FullName, batchFileId);
FileStream fs = new FileStream(localFileLocation, FileMode.Create, FileAccess.Write);
using (fs)
{
fs.Write(batchContents, 0, batchContents.Length);
}
//Save this Id to file location mapping in the mapper object
this.batchIdToFileMapper[batchFileId] = localFileLocation;
}
catch (Exception e)
{
throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("Unable to save batch file.", e));
}
}
/// <summary>
/// NOTE: This method takes in a file name as an input parameter and hence is suseptible for name canonicalization
/// attacks. This sample is meant to be a starting point in demonstrating how to transfer sync batch files and is
/// not intended to be a secure way of doing the same. This SHOULD NOT be used as such in production environment
/// without doing proper security analysis.
///
/// Please refer to the following two MSDN whitepapers for more information on guidelines for securing Web servies.
///
/// Design Guidelines for Secure Web Applications - https://msdn.microsoft.com/en-us/library/aa302420.aspx (Refer InputValidation section)
/// Architecture and Design Review for Security - https://msdn.microsoft.com/en-us/library/aa302421.aspx (Refer InputValidation section)
/// </summary>
/// <param name="batchFileId"></param>
/// <returns></returns>
public byte[] DownloadBatchFile(string batchFileId)
{
try
{
Log("DownloadBatchFile: {0}", this.peerProvider.Connection.ConnectionString);
Stream localFileStream = null;
string localBatchFileName = null;
if (!this.batchIdToFileMapper.TryGetValue(batchFileId, out localBatchFileName))
{
throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("Unable to retrieve batch file for id." + batchFileId, null));
}
localFileStream = new FileStream(localBatchFileName, FileMode.Open, FileAccess.Read);
byte[] contents = new byte[localFileStream.Length];
localFileStream.Read(contents, 0, contents.Length);
return contents;
}
catch (Exception e)
{
throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("Unable to read batch file for id " + batchFileId, e));
}
}
protected void Log(string p, params object[] paramArgs)
{
Console.WriteLine(p, paramArgs);
}
//Utility functions that the sub classes need to implement.
protected abstract RelationalSyncProvider ConfigureProvider(string scopeName, string hostName);
private void CheckAndCreateBatchingDirectory(string remotePeerId)
{
//Check to see if we have temp directory for this session.
if (sessionBatchingDirectory == null)
{
//Generate a unique Id for the directory
//We use the peer id of the store enumerating the changes so that the local temp directory is same for a given source
//across sync sessions. This enables us to restart a failed sync by not downloading already received files.
string sessionDir = Path.Combine(this.peerProvider.BatchingDirectory, "WebSync_" + remotePeerId);
sessionBatchingDirectory = new DirectoryInfo(sessionDir);
//Create the directory if it doesnt exist.
if (!sessionBatchingDirectory.Exists)
{
sessionBatchingDirectory.Create();
}
}
}
}
В следующих примерах кода из RelationalProviderProxy задаются свойства и вызываются методы веб-службы.
BatchingDirectory: позволяет приложению задавать каталог пакетной передачи для среднего уровня.
EndSession: очищает все файлы буферизации из указанного каталога.
GetChangeBatch: загружает пакеты изменений, вызывая метод DownloadBatchFile.
ProcessChangeBatch: передает пакеты изменений, вызывая метод UploadBatchFile.
public abstract class RelationalProviderProxy : KnowledgeSyncProvider, IDisposable
{
protected IRelationalSyncContract proxy;
protected SyncIdFormatGroup idFormatGroup;
protected string scopeName;
protected DirectoryInfo localBatchingDirectory;
//Represents either the SQL server host name or the CE database file name. Sql database name
//is always peer1
//For this sample scopeName is always Sales
protected string hostName;
private string batchingDirectory = Environment.ExpandEnvironmentVariables("%TEMP%");
public string BatchingDirectory
{
get { return batchingDirectory; }
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("value cannot be null or empty");
}
try
{
Uri uri = new Uri(value);
if (!uri.IsFile || uri.IsUnc)
{
throw new ArgumentException("value must be a local directory");
}
batchingDirectory = value;
}
catch (Exception e)
{
throw new ArgumentException("Invalid batching directory.", e);
}
}
}
public RelationalProviderProxy(string scopeName, string hostName)
{
this.scopeName = scopeName;
this.hostName = hostName;
this.CreateProxy();
this.proxy.Initialize(scopeName, hostName);
}
public override void BeginSession(SyncProviderPosition position, SyncSessionContext syncSessionContext)
{
this.proxy.BeginSession(position);
}
public override void EndSession(SyncSessionContext syncSessionContext)
{
proxy.EndSession();
if (this.localBatchingDirectory != null && this.localBatchingDirectory.Exists)
{
//Cleanup batching releated files from this session
this.localBatchingDirectory.Delete(true);
}
}
public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever)
{
GetChangesParameters changesWrapper = proxy.GetChanges(batchSize, destinationKnowledge);
//Retrieve the ChangeDataRetriever and the ChangeBatch
changeDataRetriever = changesWrapper.DataRetriever;
DbSyncContext context = changeDataRetriever as DbSyncContext;
//Check to see if the data is batched.
if (context != null && context.IsDataBatched)
{
if (this.localBatchingDirectory == null)
{
//Retrieve the remote peer id from the MadeWithKnowledge.ReplicaId. MadeWithKnowledge is the local knowledge of the peer
//that is enumerating the changes.
string remotePeerId = context.MadeWithKnowledge.ReplicaId.ToString();
//Generate a unique Id for the directory.
//We use the peer id of the store enumerating the changes so that the local temp directory is same for a given source
//across sync sessions. This enables us to restart a failed sync by not downloading already received files.
string sessionDir = Path.Combine(this.batchingDirectory, "WebSync_" + remotePeerId);
this.localBatchingDirectory = new DirectoryInfo(sessionDir);
//Create the directory if it doesnt exist.
if (!this.localBatchingDirectory.Exists)
{
this.localBatchingDirectory.Create();
}
}
string localFileName = Path.Combine(this.localBatchingDirectory.FullName, context.BatchFileName);
FileInfo localFileInfo = new FileInfo(localFileName);
//Download the file only if doesnt exist
FileStream localFileStream = new FileStream(localFileName, FileMode.Create, FileAccess.Write);
if (!localFileInfo.Exists)
{
byte[] remoteFileContents = this.proxy.DownloadBatchFile(context.BatchFileName);
using (localFileStream)
{
localFileStream.Write(remoteFileContents, 0, remoteFileContents.Length);
}
}
//Set DbSyncContext.Batchfile name to the new local file name
context.BatchFileName = localFileName;
}
return changesWrapper.ChangeBatch;
}
public override FullEnumerationChangeBatch GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound, SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever)
{
throw new NotImplementedException();
}
public override void GetSyncBatchParameters(out uint batchSize, out SyncKnowledge knowledge)
{
SyncBatchParameters wrapper = proxy.GetKnowledge();
batchSize = wrapper.BatchSize;
knowledge = wrapper.DestinationKnowledge;
}
public override SyncIdFormatGroup IdFormats
{
get
{
if (idFormatGroup == null)
{
idFormatGroup = new SyncIdFormatGroup();
//
// 1 byte change unit id (Harmonica default before flexible ids)
//
idFormatGroup.ChangeUnitIdFormat.IsVariableLength = false;
idFormatGroup.ChangeUnitIdFormat.Length = 1;
//
// Guid replica id
//
idFormatGroup.ReplicaIdFormat.IsVariableLength = false;
idFormatGroup.ReplicaIdFormat.Length = 16;
//
// Sync global id for item ids
//
idFormatGroup.ItemIdFormat.IsVariableLength = true;
idFormatGroup.ItemIdFormat.Length = 10 * 1024;
}
return idFormatGroup;
}
}
public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
DbSyncContext context = changeDataRetriever as DbSyncContext;
if (context != null && context.IsDataBatched)
{
string fileName = new FileInfo(context.BatchFileName).Name;
//Retrieve the remote peer id from the MadeWithKnowledge.ReplicaId. MadeWithKnowledge is the local knowledge of the peer
//that is enumerating the changes.
string peerId = context.MadeWithKnowledge.ReplicaId.ToString();
//Check to see if service already has this file
if (!this.proxy.HasUploadedBatchFile(fileName, peerId))
{
//Upload this file to remote service
FileStream stream = new FileStream(context.BatchFileName, FileMode.Open, FileAccess.Read);
byte[] contents = new byte[stream.Length];
using (stream)
{
stream.Read(contents, 0, contents.Length);
}
this.proxy.UploadBatchFile(fileName, contents, peerId);
}
context.BatchFileName = fileName;
}
this.proxy.ApplyChanges(resolutionPolicy, sourceChanges, changeDataRetriever);
}
public override void ProcessFullEnumerationChangeBatch(ConflictResolutionPolicy resolutionPolicy, FullEnumerationChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
throw new NotImplementedException();
}
protected abstract void CreateProxy();
#region IDisposable Members
public void Dispose()
{
this.proxy.Cleanup();
this.proxy = null;
GC.SuppressFinalize(this);
}
#endregion
}