本日の手前BLOGにてちょっと名前が挙がりました、「
Sync Framework」でちょっと遊びました。
Sync Frameworkって何するの?って聞かれたら、そのままです。
同期をとるプログラムを作成するんです。 ・・・・だけぢゃ、怒られちゃいますかね(^^;
まだMSDNライブラリは英語版しかないようですが、それを読んでいると
Sync Frameworkはいろいろなプラットフォームやデバイス同士が包括的に同期を取って
コラボレーションすることを目的としたフレームワークのようです。
よって以前お話した
SQL Server 2008 データ変更の追跡もこれにかかわってきます。
しかし、新しいものなのでまだあまりサンプルソースとかなくて、
とりあえず、やってみたいというノリでなぜかC#ソースをいじくってみました。
今回は、もともとMicrosoftの方から公開されているチュートリアルのソースをいぢって遊んでます。
ベースとなったコードはこちらからダウンロードできます。
see →
Tutorial - Microsoft Sync Framework Basics私が使ったのはこのうちの一番最初にある「Sync101WithMetadataStore」をちょいと変更して遊んでみました。
ソースは
こちらより落とせますが、
一応ソースを添付してみます。
まずは起動ソースから
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Synchronization;
namespace Sync101
{
class MyTestProgram
{
static void Main(string[] args)
{
string providerOneMetadata = Environment.CurrentDirectory + "\\A.Metadata";
string providerTwoMetadata = Environment.CurrentDirectory + "\\B.Metadata";
if (System.IO.File.Exists(providerOneMetadata))
{
System.IO.File.Delete(providerOneMetadata);
}
MySyncProvider providerA = new MySyncProvider("A", providerOneMetadata);
if (System.IO.File.Exists(providerTwoMetadata))
{
System.IO.File.Delete(providerTwoMetadata);
}
MySyncProvider providerB = new MySyncProvider("B", providerTwoMetadata);
providerA.Configuration.ConflictResolutionPolicy
= ConflictResolutionPolicy.ApplicationDefined;
providerB.Configuration.ConflictResolutionPolicy
= ConflictResolutionPolicy.ApplicationDefined;
providerA.DestinationCallbacks.ItemConflicting
+= new EventHandler(DestinationCallbacks_ItemConflicting);
providerB.DestinationCallbacks.ItemConflicting
+= new EventHandler(DestinationCallbacks_ItemConflicting);
//A、Bともに初期データ投入
providerA.BeginUpdates();
providerB.BeginUpdates();
providerA.CreateItem(1, "データ1A");
providerA.CreateItem(2, "データ2A");
providerB.CreateItem(3, "データ1B");
providerB.CreateItem(4, "データ2B");
providerA.EndUpdates();
providerB.EndUpdates();
Console.WriteLine("同期化前のそれぞれのデータを表示");
Console.WriteLine("======== A ========");
Console.WriteLine(providerA.ToString());
Console.WriteLine("===================");
Console.WriteLine("======== B ========");
Console.WriteLine(providerB.ToString());
Console.WriteLine("===================");
// AとBの同期化
SyncOrchestrator agent = new SyncOrchestrator();
agent.Direction = SyncDirectionOrder.DownloadAndUpload;
agent.LocalProvider = providerA;
agent.RemoteProvider = providerB;
SyncOperationStatistics stats = agent.Synchronize();
Console.WriteLine("同期化後のそれぞれのデータを表示");
Console.WriteLine("======== A ========");
Console.WriteLine(providerA.ToString());
Console.WriteLine("===================");
Console.WriteLine("======== B ========");
Console.WriteLine(providerB.ToString());
Console.WriteLine("===================");
Console.WriteLine("AのID=1に「データ1a」BのID=1に「データ1b」をセットして同期化");
Console.WriteLine("[DownloadAndUpload]");
providerA.BeginUpdates();
providerB.BeginUpdates();
providerA.UpdateItem(1, "データ1a");
providerB.UpdateItem(1, "データ1b");
providerA.EndUpdates();
providerB.EndUpdates();
agent.Direction = SyncDirectionOrder.DownloadAndUpload;
agent.LocalProvider = providerA;
agent.RemoteProvider = providerB;
stats = agent.Synchronize();
Console.WriteLine("同期化後のそれぞれのデータを表示");
Console.WriteLine("======== A ========");
Console.WriteLine(providerA.ToString());
Console.WriteLine("===================");
Console.WriteLine("======== B ========");
Console.WriteLine(providerB.ToString());
Console.WriteLine("===================");
Console.WriteLine("AのID=1に「データ1A'」BのID=1に「データ1B'」をセットして同期化");
Console.WriteLine("[UploadAndDownload]");
providerA.BeginUpdates();
providerB.BeginUpdates();
providerA.UpdateItem(1, "データ1A'");
providerB.UpdateItem(1, "データ1B'");
providerA.EndUpdates();
providerB.EndUpdates();
agent.Direction = SyncDirectionOrder.UploadAndDownload;
agent.LocalProvider = providerA;
agent.RemoteProvider = providerB;
stats = agent.Synchronize();
Console.WriteLine("同期化後のそれぞれのデータを表示");
Console.WriteLine("======== A ========");
Console.WriteLine(providerA.ToString());
Console.WriteLine("===================");
Console.WriteLine("======== B ========");
Console.WriteLine(providerB.ToString());
Console.WriteLine("===================");
Console.WriteLine("BのID=4を削除して同期化");
Console.WriteLine("[DownloadAndUpload]");
providerB.BeginUpdates();
providerB.DeleteItem(4);
providerB.EndUpdates();
agent.Direction = SyncDirectionOrder.DownloadAndUpload;
agent.LocalProvider = providerA;
agent.RemoteProvider = providerB;
stats = agent.Synchronize();
Console.WriteLine("同期化後のそれぞれのデータを表示");
Console.WriteLine("======== A ========");
Console.WriteLine(providerA.ToString());
Console.WriteLine("===================");
Console.WriteLine("======== B ========");
Console.WriteLine(providerB.ToString());
Console.WriteLine("===================");
Console.WriteLine("BのID=4を削除して同期化");
Console.WriteLine("[UploadAndDownload]");
providerB.BeginUpdates();
providerB.DeleteItem(4);
providerB.EndUpdates();
agent.Direction = SyncDirectionOrder.UploadAndDownload;
agent.LocalProvider = providerA;
agent.RemoteProvider = providerB;
stats = agent.Synchronize();
Console.WriteLine("同期化後のそれぞれのデータを表示");
Console.WriteLine("======== A ========");
Console.WriteLine(providerA.ToString());
Console.WriteLine("===================");
Console.WriteLine("======== B ========");
Console.WriteLine(providerB.ToString());
Console.WriteLine("===================");
Console.ReadLine();
}
static void DestinationCallbacks_ItemConflicting(object sender, ItemConflictingEventArgs e)
{
e.SetResolutionAction(ConflictResolutionAction.Merge);
}
}
}
そして同期処理を行っているソースです。
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Microsoft.Synchronization;
using Microsoft.Synchronization.MetadataStorage;
namespace Sync101
{
public class MySyncProvider : KnowledgeSyncProvider, IChangeDataRetriever, INotifyingChangeApplierTarget
{
SortedDictionary _store = new SortedDictionary();
SqlCeMetadataStore _metadataStore = null;
ReplicaMetadata _metadata = null;
// The provider's unique identifier.
SyncId _replicaID = null;
SyncIdFormatGroup _idFormats = null;
ReplicaKeyMap _replicaKeyMap = null;
SyncSessionContext _currentSessionContext = null;
//コンストラクタ
public MySyncProvider(string replica, string file)
{
_replicaID = new SyncId(replica);
_idFormats = new SyncIdFormatGroup();
_idFormats.ItemIdFormat.IsVariableLength = false;
_idFormats.ItemIdFormat.Length = 1;
_idFormats.ReplicaIdFormat.IsVariableLength = true;
_idFormats.ReplicaIdFormat.Length = 255;
_replicaKeyMap = new ReplicaKeyMap(_idFormats,_replicaID);
//ファイルが存在しない場合は作成、存在する場合はオープン
if (!File.Exists(file))
{
_metadataStore = SqlCeMetadataStore.CreateStore(file);
_metadata = _metadataStore.InitializeReplicaMetadata(_idFormats, _replicaID, null, null);
}
else
{
_metadataStore = SqlCeMetadataStore.OpenStore(file);
_metadata = _metadataStore.GetReplicaMetadata(_idFormats, _replicaID);
}
_metadata.SetForgottenKnowledge(new ForgottenKnowledge(_idFormats, _metadata.GetKnowledge()));
_metadata.SetKnowledge( new SyncKnowledge(_idFormats, _replicaKeyMap ,_metadata.GetNextTickCount()));
}
//デストラクタ
~MySyncProvider()
{
_metadataStore.Dispose();
}
//アイテム登録メソッド
//アイテムが存在しない場合と、存在するが削除とマークされている場合がある
public void CreateItem(byte id, string data)
{
ItemMetadata item = _metadata.FindItemMetadataById(new SyncId(id));
if (null == item)
{
//ディクショナリにデータを追加
_store.Add(id, data);
//メタデータ用のデータを作成、登録する。
item = _metadata.CreateItemMetadata(new SyncId(id), new SyncVersion(0, _metadata.GetNextTickCount()));
item.ChangeVersion = item.CreationVersion;
}
else
{
_store[id] = data;
item.ChangeVersion = new SyncVersion(0, _metadata.GetNextTickCount());
}
_metadata.SaveItemMetadata(item);
}
//アイテム更新メソッド
//バージョン更新のみの場合、data引数にnullを設定
public void UpdateItem(byte id, String data)
{
ItemMetadata item = null;
//メタデータからIDでデータの取得をし、存在しなければException
item = _metadata.FindItemMetadataById(new SyncId(id));
if (null == item)
{
throw new NotSupportedException();
}
//アイテム変更時のバージョンと変更データを設定し、メタデータに登録
item.ChangeVersion = new SyncVersion(0, _metadata.GetNextTickCount());
if( data != null ) //データ更新ありの場合
_store[id] = data;
_metadata.SaveItemMetadata(item);
}
//アイテム削除メソッド
public void DeleteItem(byte id)
{
ItemMetadata item = null;
//メタデータからIDでデータの取得をし、存在しなければException
item = _metadata.FindItemMetadataById(new SyncId(id));
if (null == item)
{
throw new NotSupportedException();
}
//アイテム変更時のバージョンと変更データを設定し、
//削除時バージョンをつけて、メタデータに登録
item.ChangeVersion = new SyncVersion(0, _metadata.GetNextTickCount());
_store[id] = "";
item.MarkAsDeleted(item.ChangeVersion);
_metadata.SaveItemMetadata(item);
}
//SyncOrchestrator.Synchronizeが呼ばれた時に使用される。
//(セッション作成後)
public override ChangeBatch GetChangeBatch(uint batchSize,
SyncKnowledge destinationKnowledge, out object changeDataRetriever)
{
ChangeBatch batch = _metadata.GetChangeBatch(batchSize, destinationKnowledge);
changeDataRetriever = this;
return batch;
}
//_metadata.GetChangeBatchの処理によって呼ばれるメソッド。
public override void GetSyncBatchParameters(out uint batchSize, out SyncKnowledge knowledge)
{
batchSize = 10;
knowledge = _metadata.GetKnowledge();
}
public override SyncIdFormatGroup IdFormats
{
get { return _idFormats; }
}
//データ同期処理の入り口
public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges,
object changeDataRetriever, SyncCallbacks syncCallback, SyncSessionStatistics sessionStatistics)
{
BeginUpdates();
IEnumerable localChanges = _metadata.GetLocalVersions(sourceChanges);
//変更処理メイン
NotifyingChangeApplier changeApplier = new NotifyingChangeApplier(_idFormats);
changeApplier.ApplyChanges(resolutionPolicy, sourceChanges, changeDataRetriever as IChangeDataRetriever, localChanges, _metadata.GetKnowledge(),
_metadata.GetForgottenKnowledge(), this, _currentSessionContext, syncCallback);
EndUpdates();
}
public object LoadChangeData(LoadChangeContext loadChangeContext)
{
string data = _store[loadChangeContext.ItemChange.ItemId.GetByteId()];
return data;
}
public ulong GetNextTickCount()
{
return _metadata.GetNextTickCount();
}
//同期処理で呼ばれるここのアイテムに対するデータ変更詳細処理
public void SaveItemChange(SaveChangeAction saveChangeAction,
ItemChange change, SaveChangeContext context)
{
switch (saveChangeAction)
{
case SaveChangeAction.Create:
this.CreateItem(change.ItemId.GetByteId(), context.ChangeData.ToString());
break;
case SaveChangeAction.UpdateVersionAndData:
this.UpdateItem(change.ItemId.GetByteId(), context.ChangeData.ToString());
break;
case SaveChangeAction.UpdateVersionOnly:
this.UpdateItem(change.ItemId.GetByteId(), null);
break;
case SaveChangeAction.DeleteAndStoreTombstone:
this.DeleteItem(change.ItemId.GetByteId());
break;
case SaveChangeAction.UpdateVersionAndMergeData:
ItemMetadata item = null;
item = _metadata.FindItemMetadataById(change.ItemId);
String data = _store[item.GlobalId.GetByteId()];
if ( context.ChangeData != null )
data += context.ChangeData.ToString();
this.UpdateItem(change.ItemId.GetByteId(), data);
break;
}
}
public void StoreKnowledgeForScope(SyncKnowledge knowledge, ForgottenKnowledge forgottenKnowledge)
{
_metadata.SetKnowledge(knowledge);
_metadata.SetForgottenKnowledge(forgottenKnowledge);
_metadata.SaveReplicaMetadata();
}
//デバッグ用出力だそうです。
public override string ToString()
{
string s = string.Format("\nProvider {0} has the following for Knowledge: {1}\n", _replicaID.GetStringId(),_metadata.GetKnowledge().ToString());
foreach( byte b in _store.Keys )
{
s += String.Format("[{0}]: {1}", b, _store[b]);
s+= Environment.NewLine;
}
return s;
}
public IChangeDataRetriever GetDataRetriever()
{
return this;
}
public bool TryGetDestinationVersion(ItemChange sourceChange, out ItemChange destinationVersion)
{
destinationVersion = null;
return false;
}
//トランザクション制御
public void BeginUpdates()
{
_metadataStore.BeginTransaction();
}
public void EndUpdates()
{
_metadataStore.CommitTransaction();
}
//セッション制御
public override void BeginSession(SyncProviderPosition position, SyncSessionContext syncSessionContext)
{
_currentSessionContext = syncSessionContext;
}
public override void EndSession(SyncSessionContext syncSessionContext)
{
_currentSessionContext = null;
}
//未使用だけど、書くべきメソッド達
public void SaveChangeWithChangeUnits(ItemChange change, SaveChangeWithChangeUnitsContext context)
{
throw new NotImplementedException();
}
public void SaveConflict(ItemChange conflictingChange, object conflictingChangeData, SyncKnowledge conflictingChangeKnowledge)
{
throw new NotImplementedException();
}
public override FullEnumerationChangeBatch GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound, SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever)
{
throw new NotImplementedException();
}
public override void ProcessFullEnumerationChangeBatch(ConflictResolutionPolicy resolutionPolicy, FullEnumerationChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallback, SyncSessionStatistics sessionStatistics)
{
throw new NotImplementedException();
}
}
}
そして実行結果です。
同期化前のそれぞれのデータを表示
======== A ========
Provider A has the following for Knowledge: Scope: [(0:3)]
[1]: データ1A
[2]: データ2A
===================
======== B ========
Provider B has the following for Knowledge: Scope: [(0:3)]
[3]: データ1B
[4]: データ2B
===================
同期化後のそれぞれのデータを表示
======== A ========
Provider A has the following for Knowledge: Scope: [(0:5) (1:3)]
[1]: データ1A
[2]: データ2A
[3]: データ1B
[4]: データ2B
===================
======== B ========
Provider B has the following for Knowledge: Scope: [(0:7) (1:5)]
[1]: データ1A
[2]: データ2A
[3]: データ1B
[4]: データ2B
===================
AのID=1に「データ1a」BのID=1に「データ1b」をセットして同期化
[DownloadAndUpload]
同期化後のそれぞれのデータを表示
======== A ========
Provider A has the following for Knowledge: Scope: [(0:11) (1:8)]
[1]: データ1aデータ1b
[2]: データ2A
[3]: データ1B
[4]: データ2B
===================
======== B ========
Provider B has the following for Knowledge: Scope: [(0:12) (1:11)]
[1]: データ1aデータ1b
[2]: データ2A
[3]: データ1B
[4]: データ2B
===================
AのID=1に「データ1A'」BのID=1に「データ1B'」をセットして同期化
[UploadAndDownload]
同期化後のそれぞれのデータを表示
======== A ========
Provider A has the following for Knowledge: Scope: [(0:16) (1:15)]
[1]: データ1B'データ1A'
[2]: データ2A
[3]: データ1B
[4]: データ2B
===================
======== B ========
Provider B has the following for Knowledge: Scope: [(0:15) (1:12)]
[1]: データ1B'データ1A'
[2]: データ2A
[3]: データ1B
[4]: データ2B
===================
BのID=4を削除して同期化
[DownloadAndUpload]
同期化後のそれぞれのデータを表示
======== A ========
Provider A has the following for Knowledge: Scope: [(0:18) (1:16)]
[1]: データ1B'データ1A'
[2]: データ2A
[3]: データ1B
[4]: データ2B
===================
======== B ========
Provider B has the following for Knowledge: Scope: [(0:20) (1:18)]
[1]: データ1B'データ1A'
[2]: データ2A
[3]: データ1B
[4]: データ2B
===================
BのID=4を削除して同期化
[UploadAndDownload]
同期化後のそれぞれのデータを表示
======== A ========
Provider A has the following for Knowledge: Scope: [(0:22) (1:21)]
[1]: データ1B'データ1A'
[2]: データ2A
[3]: データ1B
[4]:
===================
======== B ========
Provider B has the following for Knowledge: Scope: [(0:21) (1:18)]
[1]: データ1B'データ1A'
[2]: データ2A
[3]: データ1B
[4]:
===================
とまぁ、こんな感じになるらしいです。
同期の方向と更新パターンが見えてくればあとはロジックをどう組むかって部分に注視できるかなぁって
そういう感じのサンプルにでもなれば幸いです。
ってまた長い内容でごめんなさいです。
もともとのサンプルはUpdateとDeleteがいっしょくたのメソッドになっていたり、気に食わない部分が多々あったので、
私の趣味によって書き直していたりします。
どっちがいいかは・・・・・どうなんでしょうね?
とりあえず、おいらのJavaちっくなC#をわらってくれぃ(w