WCFサービスを通じて、独立したセルフトラッキングエンティティの非同期的に怠zyなナビゲーションプロパティ?
-
28-10-2019 - |
質問
自己追跡エンティティをMVVMを使用して構築したWPFアプリケーションに渡すWCFクライアントがあります。アプリケーション自体には動的なインターフェイスがあります。ユーザーは、自分の役割やタスクがどのようなタスクであるかに応じて、作業領域で表示したいオブジェクトを選択できます。
私の自己追跡エンティティにはかなりの数のナビゲーションプロパティがあり、それらの多くは必要ありません。これらのオブジェクトの一部は非常に大きい可能性があるため、リクエストに応じてこれらのプロパティのみをロードしたいと思います。
私のアプリケーションは次のようになります:
[WCF] <---> [ClientSide Repository] <---> [ViewModel] <---> [View]
私のモデルは自己追跡エンティティです。クライアント側のリポジトリは、モデルをリクエストしたViewModelに戻す前に、Lazyloadメソッド(必要に応じて)を接続します。すべてのWCFサービス呼び出しは非同期です。つまり、Lazyloadメソッドも同意していません。
Lazyloadの実際の実装は、私にいくつかのトラブルを与えています。これが私が思いついたオプションです。
編集 - コードサンプルを削除して、これを読みやすくして理解しやすくします。あなたがそれを見たい場合は、質問の以前のバージョンを参照してください
オプションa
ゲッターのWCFサーバーからモデルのプロパティを非同期的に怠る
良い: オンデマンドでデータの読み込みは非常に簡単です。 XAMLのバインディングによりデータがロードされるため、コントロールが画面上にある場合、データは非同期にロードされ、UIがそこにあるときにUIに通知します。そうでない場合、何もロードされません。例えば、 <ItemsControl ItemsSource="{Binding CurrentConsumer.ConsumerDocuments}" />
データはロードされますが、インターフェイスのドキュメントセクションがそこにない場合、何もロードされません。
悪い: 空のリストを返すため、開始される前にこのプロパティを他のコードで使用することはできません。たとえば、ドキュメントが読み込まれていない場合、次の呼び出しは常にfalseを返します。
public bool HasDocuments
{
get { return ConsumerDocuments.Count > 0; }
}
オプションb
必要に応じて、データをロードするために手動で電話をかけます
良い: 簡単に実装します - 追加するだけです LoadConsumerDocumentsSync()
と LoadConsumerDocumentsAsync()
方法
悪い: バインディングで使用される場合を含め、アクセスしようとする前にデータをロードすることを忘れないでください。これは簡単に思えるかもしれませんが、すぐに手に負えなくなる可能性があります。たとえば、各ConsumerDocumentにはユーザーが作成され、ユーザーLastModifiedがあります。拡張機能、電子メール、チーム、ロールなどの追加のユーザーデータを表示するツールチップを使用してUsermodelを定義するデータセットがあります。そのため LoadDocuments
, 、次にそれらをループして呼び出します LoadConsumerModified
と LoadConsumerCreated
. 。それも続けることができます...その後、私はしなければなりません LoadUserGroups
と LoadUserSupervisor
. 。また、のようなものがある円形ループのリスクも実行します User
があります Groups[]
プロパティ、a Group
があります Users[]
財産
オプションc
これまでの私のお気に入りのオプション...プロパティにアクセスする2つの方法を作成します。 1つの同期と1つの非同期。バインディングはAsyncプロパティに対して行われ、任意のコードはSyncプロパティを使用します。
良い: データは必要に応じて非同期にロードされます - まさに私が望むものです。これらの追加プロパティ/メソッドを生成するためにT4テンプレートを変更するだけであるため、それほど余分なコーディングはありません。
悪い: 同じデータにアクセスする2つの方法があることは、非効率的で混乱を招くようです。いつ使用すべきか覚えておく必要があります Consumer.ConsumerDocumentsAsync
それ以外の Consumer.ConsumerDocumentsSync
. 。また、WCFサービスコールが複数回実行される可能性もあり、これにはiSconsumerDocumentsedがロードされるなど、すべてのナビゲーションプロパティに追加のISLOADプロパティが必要です。
オプションd
Asyncronousの負荷をスキップし、セッターにすべてを同期してロードするだけです。
良い: 非常にシンプルで、余分な作業は必要ありません
悪い: データが読み込まれるとUIがロックされます。これを望まない。
オプションe
誰かにいるので、これを行う別の方法があると言って、サンプルをコードするように指摘してください:)
その他のメモ
ナビゲーションプロパティの一部は、クライアントにオブジェクトを返す前にWCFサーバーにロードされますが、他のナビゲーションはそれを行うには高すぎます。
オプションCのロードイベントを手動で呼び出すことを除いて、これらはすべてT4テンプレートを介して実行できますので、私がやるべきコーディングはほとんどありません。私がしなければならないのは、クライアント側のリポジトリにLazyloadイベントを接続し、適切なサービスコールを指すことです。
解決 3
私が思いついたソリューションは、自己追跡エンティティのT4テンプレートを変更して、以下に示す変更を行うことでした。これを読みやすくするために実際の実装が省略されていますが、プロパティ/メソッド名はすべてが何をするかを明確にする必要があります。
古いT4生成ナビゲーションプロパティ
[DataMember]
public MyClass MyProperty { get; set;}
private MyClass _myProperty;
新しいT4生成ナビゲーションプロパティ
[DataMember]
internal MyClass MyProperty {get; set;}
public MyClass MyPropertySync {get; set;}
public MyClass MyPropertyAsync {get; set;}
private MyClass _myProperty;
private bool _isMyPropertyLoaded;
private async void LoadMyPropertyAsync();
private async Task<MyClass> GetMyPropertyAsync();
private MyClass GetMyPropertySync();
私は、同じ私有財産を指す、プロパティの3つのコピーを作成しました。内部コピーはEF用です。おそらくそれを取り除くことができますが、EFはその名前でプロパティを期待しているので、EFを修正して新しいプロパティ名を使用するよりも、そのままにしておくのが最も簡単です。クラスの名前空間以外のものは何も使用したくないので内部です。
プロパティの他の2つのコピーは、値がロードされたらまったく同じ方法で実行されますが、プロパティを異なる方法でロードします。
Asyncバージョンが実行されます LoadMyPropertyAsync()
, 、単に実行されます GetMyPropertyAsync()
. 。私は置くことができないので、これに2つの方法が必要でした async
ゲッターのモディファイア、および非アジンメソッドから呼び出す場合はボイドを返す必要があります。
同期バージョンは実行されます GetMyPropertySync()
順番に実行されます GetMyPropertyAsync()
同期して
これはすべてT4生成されているため、WCFサービスからエンティティが取得されたときにAsync Lazy Load Delegateを接続する以外に、私は何かをする必要はありません。
私のバインディングは、プロパティの非同期バージョンを指しており、その他のコードはプロパティの同期バージョンを指し、どちらも追加のコーディングなしで正しく機能します。
<ItemsControl ItemsSource="{Binding CurrentConsumer.DocumentsAsync}" />
CurrentConsumer.DocumentsSync.Clear();
他のヒント
まず第一に、この問題に対する読者への明確な解決策を提供する必要があると言わざるを得ません。ユーザーにバインドすると非同期がロードされています。解決。そのような動作が順調であると言う場合、私たちはその意図についてRESTコードを非常に明確にしておく必要があるので、データにどのようにアクセスしようとしているのかを見ることができます - 何かの冗長な名前(Method、className、SMTHを介して非同期または同期するそうしないと)。
したがって、古い.assynchronized()アプローチに近いソリューションを使用し、デコレータークラスを作成し、各プロパティにプライベート/保護された非施設と同期メソッドを提供することができると思います。クラス、より多くのアプリロイトであるものは何でも。
同期デコレータでクラスを飾ると、各レイジーロード可能なクラスを同期デコレーターで包みます。SYNCHUSER(user).documents.count on sync classバージョンでsynchuser(user synchuser(user)のように使用できるようになります。 ).syncDocuments(ドキュメント)。ドキュメントプロパティの過負荷バージョンの背後にあり、同期ゲッター関数を呼び出します。
同期バージョンと非同期バージョンの両方が同じオブジェクトで動作するため、このアプローチは、プロパティを変更する場合、他のオブジェクトではないオブジェクトを変更することにつながりません。
あなたのタスクは、魔法の「美しくシンプルな」方法で解決できるものとして聞こえるかもしれませんが、私はそれができるとは思わない、またはそれがこれよりもシンプルではないとは思わないでしょう。
これがうまくいかない場合は、同期または非同期バージョンのクラスが使用されているかどうか、またはコードベースを維持するのが非常に難しいかどうかにかかわらず、コードを異なる明確な方法が必要であることを100%確信しています。
オプションa 解決策である必要があります。
名前の1つのプロパティを作成します LoadingStatus データが読み込まれているか、まだロードされていないことを示します。データを非同期にロードし、それに応じてLoadingStatusプロパティを設定します。
各プロパティの読み込みステータスを確認し、データがロードされていない場合は、関数を呼び出してデータを読み込み、 逆に.
可能性があります Binding.IsAsync
ライブラリプロパティはここで役立ちますか?
編集:少し拡張してください。最初の使用時にWCFサービスを呼び出す怠zyなロードされた同期プロパティを用意します。その後、非同期結合により、UIがブロックされなくなります。
この質問はしばらく前に尋ねられましたが、Async-awaitキーワードリストの最上部に近づいており、.NET 4.5で非常に異なる方法で回答されると思います。
これは完璧なユースケースになると思います AsyncLazy<T>
いくつかのサイトで説明されているタイプ:
http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html http://blog.stephencleary.com/2013/01/async-oop-3-properties.html
頭の中に2つの考えがあります。
1)実装 IQueryable<>
の応答 WCF
サービス。そして、andでdbに到着します IQueryable<>
パターン。
2)クライアントリポジトリで、 ConsumerDocuments
データを取得するためのプロパティ。
private IEnumerable<ConsumerDocuments> _consumerDocuments;
public IEnumerable<ConsumerDocuments> ConsumerDocuments
{
get
{
return _consumerDocuments ?? (_consumerDocuments = GetConsumerDocuments() );
}
}
私が見るように、ViewModelは利用可能なデータがあるかどうかを認識する必要があります。データが取得されている間にデータなしで無意味なUI要素を非表示または無効にして、データが到着したときにそれらを表示することができます。
一部のデータをロードする必要があることを検出するため、UIを「待機」モードに設定し、Async Fetchをキックオフし、データが入っているときに待機モードから外れます。おそらく、viewmodelに、興味のあるオブジェクトの「loadcompleted」イベントを購読することによって。
(編集)各モデルオブジェクトの状態を追跡することにより、過度の負荷または円形の依存関係を回避できます:アンロード/ロード/ロード。
これがあなたのためのオプションeです。
非同期にデータをロードします。ゆっくりとオブジェクト全体を埋めるAAバックグラウンドスレッドで、最初のフェッチキューを掲載します。また、データを舞台裏でロードする必要がある方法を、負荷仕上げでブロックするようにします。 (ブロッキング と 必要なデータが優先度が高いことをバックグラウンドスレッドに通知させ、次にそれらを取得してください。
これにより、可能な限りすぐに応答するUIが得られます。コードを書き、ロードされたものを考えない能力が得られ、ほとんどが機能します。一つのがんは、データが読み込まれている間にブロッキングコールをすることがありますが、うまくいけばそれがあまり頻繁に行われないことです。そうした場合、最悪の場合、データのブロッキングフェッチと、そこにあるかどうかを確認するために投票する能力があるオプションCのようなものに分解します。しかし、ほとんどの場合、それについてあまり心配する必要はありません。
免責事項:私は個人的にWindowsを使用しておらず、ほとんどの時間をUISから遠く離れたバックエンドで作業しています。アイデアが気に入ったら、お気軽にお試しください。しかし、私は実際にこの戦略に従って、AjaxがダイナミックなWebページで呼び出す舞台裏よりも複雑なものに従っていません。