データシンクロナイザー。 開発者への注意

職場や日常生活では、さまざまな種類のデータ同期を頻繁に処理する必要があります 。 電話機のファイルとフォルダーをコンピューターと同期し、バージョン管理システムで通常のアクションを実行し、連絡先、メールメッセージ、ドキュメントを同期するためにさまざまなオンラインサービスを使用し、特定のケースでこのプロセスがどのように実装されるかについても考えないことがよくあります。



独自のシンクロナイザーを作成する場合は、多くの質問に遭遇する可能性が高いでしょう。 この記事では、このようなコンポーネントを作成した経験を共有し、その要件を検討します。 これらの要件は、ユーザーから受け取ったあらゆる種類の要望と、 XtraSchedulerスケジューライベントシンクロナイザーを使用するための実際のシナリオに基づいています。 したがって、コード例として、指定された製品のコードの断片を提供します。



最初に、同期プロセスに参加するオブジェクトを決定します。

これらは2つのデータセット(ソースおよびターゲット/最終)と、これらのセットに対して多数の操作を実行するシンクロナイザーであり、その結果、実装されるシナリオに従ってターゲットセットが変更される必要があります。



シンクロナイザーオブジェクト


同期スクリプトは、シンクロナイザーが持つ機能を決定します。 同期が1つの「メイン」コピーのみを暗示し、内容を別の内容に完全に置き換えることによって実行される場合、インポーター/エクスポータータイプの単純なバージョンが適しています。 独立したレコードのセットを同期する場合、シンクロナイザーのより複雑な実装を作成する必要があります。



基本クラス


すべての子孫の共通の動作と、メソッド、プロパティ、およびイベントのインターフェイスを定義するベースシンクロナイザークラスを作成します。 このようなクラス、たとえばSynchronizerBaseを呼び出します。 このようなクラスは、抽象メソッドの呼び出しの厳密に定義された順序を定義して、基本的な必要な同期アクションを目的の順序で実行できます。 機能は継承により拡張されます。 基本クラスを持つことで、コードの重複から解放されます。 内部構造やプロパティの初期化などの一般的な操作は、このクラスで一度実装できます。



このアプローチの追加の利点は、その後に実装されるすべての相続人に対して単一の一貫したAPIを受け取るという事実です。



public abstract class SynchronizerBase { //... protected SynchronizerBase(StorageBase storage) {...} public event ExchangeExceptionEventHandler OnException; protected internal abstract void SynchronizeCore(); public virtual void Terminate() {...} public virtual void Synchronize() { Storage.BeginUpdate(); try { ResetTermination(); SynchronizeCore(); } finally { Storage.EndUpdate(); } } }
      
      







職務分離


データセットを使用するシナリオに応じて、どのセットが「メイン」セットであるかを事前に把握して、スクリプトに記載された一連のアクションを「実行」できるSynchronizerBaseの子孫を実装できます。 このような特殊なクラスは、「すべてを一度に知っている」唯一のクラスを設定して使用するよりもはるかに使いやすいでしょう。



そのため、ImportSynchronizerやExportSynchronizerなど、いくつかの子孫を作成できます。これらは、各シナリオの基本的な同期ロジックを実装します。 将来、さまざまなデータセットの特定の子孫を実装する予定がある場合、これらのクラスは抽象のままにすることができます。



たとえば、 XtraSchedulerで次の基本クラススキームを取得しました。







サブクラス化アルゴリズム


シンクロナイザーオブジェクトの負荷が高くなりすぎないようにするには、同期アルゴリズムの実装をシンクロナイザー自体のインターフェイスから分離することにより、アーキテクチャを編成するのが理にかなっています。 これらのオブジェクト間の相互作用の確立を忘れずに、シンクロナイザークラスのサブクラスを選択します。



 public class ImportSynchronizeManager : ImportManager { public ImportSynchronizeManager(AppointmentExchanger synchronizer) : base(synchronizer) { } protected internal override void PerformExchange() { Appointments.BeginUpdate(); try { PrepareSynchronization(); SynchronizeAppointments(); DeleteNonSynchronizedAppointments(); } finally { Appointments.EndUpdate(); } } } public class ExportSynchronizeManager : ExportManager { public ExportSynchronizeManager(AppointmentExchanger synchronizer) : base(synchronizer) { } protected internal override void PerformExchange() { PrepareSynchronization(); SynchronizeOutlookAppointments(); CreateNewOutlookAppointments(); } }
      
      







データセットオブジェクトの操作


同期シナリオに応じて、データセットをさまざまな方法で変更できます。 ただし、原則として、オブジェクトが設定されているすべてのアクションは、3つの主なアクションに縮小されます。



上記のすべての操作は、次の表にまとめることができます。







説明したアクションは、シンクロナイザーの各相続人に何らかの形で実装する必要があります。 同時に、重要な点を考慮する必要があります。データのどのコピーが「メイン」と見なされます。 選択内容によっては、ソースセットとターゲットセットの場所が変わる場合があります。 これが、ImportSynchronizerクラスとExportSynchronizerクラスがデータセットに対して反対のアクションを実行する理由です。



イベントサポート


間違いなく、設定されたオブジェクトに対して何らかのアクションを実行する場合、ユーザーは操作の「前」と「後」にこれらのオブジェクトにアクセスできることを望みます。 基本クラスで同期イベントと同期イベントのペアを整理します。



SynchronizingEventArgsおよびSynchronizedEventArgsイベントハンドラーの引数を定義し、同期セットから対応するオブジェクトのフィールドとプロパティを追加します。 基本クラスでこれをすぐに実行できない場合は、引数の継承を使用して、継承者に不足しているプロパティを作成します。



 public class AppointmentEventArgs : EventArgs { public AppointmentEventArgs(Appointment apt) { } public Appointment Appointment { get { ... } } } public class AppointmentCancelEventArgs : AppointmentEventArgs { public AppointmentCancelEventArgs(Appointment apt) : base(apt) { } public bool Cancel { get { ... } } } public class AppointmentSynchronizingEventArgs : AppointmentCancelEventArgs { public AppointmentSynchronizingEventArgs(Appointment apt) : base(apt) { } public SynchronizeOperation Operation { get { ... } } } public class OutlookAppointmentSynchronizingEventArgs : AppointmentSynchronizingEventArgs { public OutlookAppointmentSynchronizingEventArgs(Appointment apt, _AppointmentItem olApt) : base(apt) { } public _AppointmentItem OutlookAppointment { get { ... } } }
      
      







例外を処理するイベントの必要性を考慮してください。 たとえば、OnExceptionイベントを実装し、受信した例外をそこにリダイレクトできます。 例外が発生した後、同期プロセスを続行するかどうかをユーザーに決定させます。 必要なすべての情報を使用してイベント引数を完了します。



 iCalendarImporter importer; //... importer.OnException += new ExchangeExceptionEventHandler(importer_OnException); void importer_OnException(object sender, ExchangeExceptionEventArgs e) { iCalendarParseExceptionEventArgs args = e as iCalendarParseExceptionEventArgs; if (args != null) { ShowErrorMessage(String.Format("Cannot parse line {0} with text '{1}'", args.LineIndex, args.LineText)); } else ShowErrorMessage(e.OriginalException.Message); e.Handled = true; // prevent this exception from throwing } }
      
      







データセキュリティ


セットオブジェクトの各操作をキャンセルする機能をサポートします。 これを行うには、CancelプロパティをSynchronizingEventArgsイベントの引数に追加します。



これは、最初に一方向に、次に他の方向に同期するときにすべての削除操作をキャンセルすることが理にかなっている場合、2つの独立したデータセットを「マージ」するシナリオに必ず役立つことを確認してください。



 AppointmentImportSynchronizer synchronizer; //... synchronizer.AppointmentSynchronizing += new AppointmentSynchronizingEventHandler(synchronizer_AppointmentSynchronizing); void synchronizer_AppointmentSynchronizing(object sender, AppointmentSynchronizingEventArgs e) { // ... if (ShouldCancelOperation) e.Cancel = true; }
      
      







すべてのオブジェクト操作シナリオをカバー


オブジェクトに対して提案された「デフォルト」アクションをキャンセルするだけでなく、別の可能な操作を可能にする状況を考慮してください。



たとえば、オプションのSynchronizeOperation {Create、Replace、Delete}プロパティを引数に追加し、ユーザーに目的の値を指定させます。 したがって、元のセットのコピーで置き換えるのではなく、ターゲットデータセットのオブジェクトを削除できます。



このアプローチにより、データセットの「編集の競合」をより正確に処理できます。



 void synchronizer_AppointmentSynchronizing(object sender, AppointmentSynchronizingEventArgs e) { switch (e.Operation) { case SynchronizeOperation.Replace: if (ShouldDeleteInsteadReplace.Checked) e.Operation = SynchronizeOperation.Delete; break; }
      
      







UI要素


メッセージ、ダイアログ、その他の視覚要素の表示を使用しないように、シンクロナイザーを設計してください。 いつものように、コンポーネントを使用する必要のあるユーザーがいるため、アプリケーションまたは特別なサービスなどから作成されたストリームで同期を実行します。



データキャッシング


オブジェクトをシンクロナイザーに保存して、データセットをロードするのが理にかなっている場合があります。 これは、実際のデータセットへの繰り返しの呼び出しを避けることが望ましい場合に意味があります。 さらに、同期プロセス中に、ターゲットデータセットが変更される場合があります。これは、シンクロナイザーコードからこのセットを反復処理することに関連する問題に現れる可能性があります。



データセットのパラメーター化


遅かれ早かれ、ユーザーはセット全体ではなく、時間間隔や特定のパラメーターなどによって制限されたセットの一部のみを同期することになります。



解決策は、ユーザーがロジックを決定するプロバイダーを「スリップ」する機能を持つソースデータプロバイダーを作成することです。 この場合、シンクロナイザーはデータを直接ではなく、プロバイダーを介して受信する必要があります。 この場合、シンクロナイザー内で上記のデータキャッシュを使用する必要があります。



 void DoFilteredImport() { OutlookImport importer = new OutlookImport(this.schedulerStorage1); TimeInterval interval = CalculateCurrentWeekInterval(); ParameterizedCalendarProvider provider = new ParameterizedCalendarProvider(interval); importer.SetCalendarProvider(provider); importer.Import(System.IO.Stream.Null); }
      
      







プロセス完了


ユーザーの要求に応じて同期プロセスを完了することを検討してください。 たとえば、基本クラスSynchronizerBaseのTerminateメソッドを取得します。 このような関数は、大きなデータセットの同期中に例外が発生した場合、またはセットオブジェクトが特定の条件を満たしていないため、さらなる実行を直ちに中断する必要がある場合に役立ちます。 この場合、ユーザーはプロセスの終了を待つ必要がなくなります。



 void synchronizer_OnException(object sender, ExchangeExceptionEventArgs e) { // ... AppointmentImportSynchronizer synchronizer = (AppointmentImportSynchronizer)sender; synchronizer.Terminate(); e.Handled = true; }
      
      







拡張性


ユーザーがコンポーネントによって同期されるプロパティ以外のプロパティを同期したい場合があります。 カスタムプロパティまたはオブジェクトを設定するレベルでこれを定義する機能を提供し、これに必要なすべてのAPIを提供します。



 void exporter_AppointmentExporting(object sender, AppointmentExportingEventArgs e) { iCalendarAppointmentExportingEventArgs args = e as iCalendarAppointmentExportingEventArgs; AddEventAttendees(args.VEvent, “test@corp.com”); } private void AddEventAttendee(VEvent ev, string address) { TextProperty p = new TextProperty("ATTENDEE", String.Format("mailto:{0}", address)); p.AddParameter("CN", address); p.AddParameter("RSVP", "TRUE"); ev.CustomProperties.Add(p); }
      
      







ユーザーがシンクロナイザーから継承して機能をオーバーライドできるようにします。 クラスとメソッドを設計するとき、これを考慮してください。



 public class MyOutlookImportSynchronizer : OutlookImportSynchronizer { public MyOutlookImportSynchronizer(SchedulerStorageBase storage) : base(storage) { } protected override OutlookExchangeManager CreateExchangeManager() { return new MyExchangeManager(); } }
      
      







ヘルパークラス


シンクロナイザー初期化パラメーター(たとえば、特定のカレンダーに示されるディレクトリー)または同期に必要なその他の情報を取得するメソッドを作成します。 より便利に使用するには、それらを別々のヘルパークラスに入れるか、コンポーネントで直接利用できるようにします。



 public class OutlookExchangeHelper { public static string[] GetOutlookCalendarNames(); public static string[] GetOutlookCalendarPaths() ; public static List<OutlookCalendarFolder> GetOutlookCalendarFolders(); } public class OutlookUtils { public static _Application GetOutlookApplication(); public static void ReleaseOutlookObject(object obj); } public class iCalendarConvert { public static TimeSpan ToUtcOffset(string value); public static string FromUtcOffset(TimeSpan value) ; public static string UnescapeString(string str); }
      
      







上記の「ヒント集」が、シンクロナイザを作成する前に必要な機能の量を決定するのに最も役立ち、その実装プロセスでのエラーを回避できることを願っています。



All Articles