5

永続ストレージにリレーショナル データベースを使用するカスタムNSIncrementalStoreサブクラスを実装しています。私がまだ苦労していることの 1 つは、楽観的ロックのサポートです。


(以下の私の質問にこの長い説明を飛ばしてください)

Core Data の SQLite 増分ストアが生成する SQL ログを調べることで、この問題にどのようにアプローチするかを分析し、次の結論に達しました。

  • データベース内の各エンティティ テーブルには、このエンティティ (行) の特定のインスタンスが変更された回数を 1 (最初の挿入) から始まるZ_OPT列があります。

  • 管理オブジェクトが変更されるたびに、対応するデータベース行のZ_OPT値が増加します。

  • ストアは、 NSIncrementalStoreNodeインスタンスのキャッシュ ( Core Data ドキュメントでは行キャッシュと呼ばれます) を維持します。それぞれのインスタンスは、管理対象オブジェクトの行に対する前のSELECTまたはUPDATE SQL クエリによって返されたZ_OPT値と等しいバージョンプロパティを持ちます。

  • 管理対象オブジェクトが NSManagedObjectContext から返される(たとえば、NSFetchRequestを実行することによって)、MOC はこのバージョン番号を含むこのオブジェクトのスナップショットを作成します。

  • オブジェクトが変更または削除されると、Core Data は、キャッシュされた行とオブジェクトのスナップショットのバージョンを比較して、コンテキスト外で変更または削除されていないことを確認します。これはすべて、オブジェクトが属するコンテキストで-save:が呼び出されたときに発生します。バージョンが異なる場合、マージ競合が検出され、設定されたマージ ポリシーに基づいて処理されます。

MOC が保存されると、変更/削除されたオブジェクトごとに-newValuesForObjectWithID:withContext:error:メソッドが呼び出され、バージョン番号とともにNSIncrementalStoreNodeが返されます。次に、このバージョンがスナップショットのバージョンと比較され、それらが異なる場合、保存は適切なマージ競合で失敗します (少なくともデフォルトのマージ ポリシーでは)。

-newValuesForObjectWithID:withContext:error:オブジェクトが同じストア インスタンスを使用して他のコンテキストで同時に変更された場合、行キャッシュを最初にチェックするため、この単純な使用例は私のストアで適切に機能します。この場合、競合を検出するのに十分な、より高いバージョン番号を持つ更新された行がキャッシュに含まれます。

しかし、同じデータベース ファイルを使用する他のアプリケーションまたは他のストア インスタンスによって、基になるデータベースがストア外で変更されたことをどのように検出できますか? これはめったにない特殊なケースであることはわかっていますが、Core Data はそれを適切に処理しており、私も同じようにしたいと考えています。

Core Data のストアは、次のような SQL クエリを使用して、オブジェクトの行を更新/削除します。

UPDATE ZFOO SET Z_OPT=Y, (...) WHERE (...) AND Z_OPT=X
DELETE FROM ZFOO WHERE (...) AND Z_OPT=X


X - (キャッシュから) ストアに最後に認識されたバージョン番号
Y - 新しいバージョン番号

このようなクエリが失敗した場合 (影響を受ける行がない場合)、行はストアのキャッシュで更新され、そのバージョンが以前にキャッシュされたものと比較されます。


私の質問は次のとおりです。カスタムNSIncrementalStoreは、一部の更新/削除/ロックされたオブジェクトで楽観的ロックの失敗が発生したことを Core Data にどのように通知できますか? NSSaveChangesRequestを処理するときに-executeRequest:withContext:error:メソッドが渡されたことを認識できるのはストアだけです。

基礎となるデータベースがストアの下で変更されない場合、Core Data が-newValuesForObjectWithID:withContext:error:を変更/削除/ロックされたオブジェクトごとに呼び出してから、ストアで変更の保存要求を実行するため、競合が検出されます。NSIncrementalStoreが保存要求の処理を開始したに楽観的ロックの失敗が発生したことを Core Data に通知する方法を見つけることができませんでした。それを行うための文書化されていない方法はありますか?その場合、Core Data はいくつかの例外をスローするように見えますが、これは魔法のように失敗した保存要求に変換され、NSError がすべての競合を一覧表示します。-executeRequest:withContext:error:から nil を返すことによってのみ、部分的に模倣することができます。自分でエラーメッセージを作成します。このシナリオでも、標準の Core Data 競合処理メカニズムを使用する方法が必要だと思います。

4

2 に答える 2

1

これはあなたの質問に対する答えではないことは承知していますが、CoreData とデータベースとの相関関係に関する私の見解をお伝えします。

(第 1 レベルのキャッシュ)
NSPesistentStoreCoordinator + NSPersistentStore == データベースへの単一接続

(第 2 レベルのキャッシュ)
NSManagedObjectContext == 変更を保持する接続を介したキャッシュ

したがって、私が理解している問題は、ストアへの複数の接続があり、それぞれが変更を加えていますが、レコードを中央でバージョン管理していないことです。-executeRequest:withContext:error:レコードのバージョンが一致するNSSaveRequestType
ことを確認する責任があります。接続レベル (レベル 1) で競合が見つかった場合は、コンテキスト (レベル 2) とコーディネーターの間でバージョンの不一致を報告します。
接続 (レベル 1) とストアの間でバージョンの不一致を報告する必要があります。
これを行うには、ストアへのすべての接続 (ConnectionManager) でストアの変更を報告する必要があります。または、ストアで実行された変更へのフックを提供する場合があります。
私は SQLite の専門家ではありませんが、SQLite API にはその分野で提供できるものがあります:
更新フック
コミット フック
の変更
合計の変更
(私はこれらの種類のフックを設定した経験はありませんが、CoreData がそれらを使用する場合、デバッグ ログには表示されません)

これらのエラーを報告するには、エラー ポインター (NSError**) を設定し、その内部データを CoreData コーディネーターが設定しているものと一致するように設定します (マージ競合を作成し、必要に応じてそれらの情報を設定します)。

楽観的ロックの失敗は、-executeRequest:withContext:error: (ストアへの不正な接続があり、マネージャーによって追跡されていない場合を除きます。) の間にのみ発生することに注意して
ください。パフォーマンス コスト] を使用するか、最近レコードに加えられた変更にいくつかのフックを使用します)

ストアへの複数の接続を処理するには、ストアの URL をキーとする NSIncrementalStoreNode の共有キャッシュが必要になる場合があります:
static @{
url1 : actualCacheMapping1, url2 : actualCacheMapping2
,
...
}
URL の実際のキャッシュを保存します。

これがあなたにとって意味があることを願っています。

于 2013-04-05T11:17:30.537 に答える
1

私の質問は次のとおりです。カスタム NSIncrementalStore は、一部の更新/削除/ロックされたオブジェクトで楽観的ロックの失敗が発生したことを Core Data にどのように通知できますか? NSSaveChangesRequest を処理するときに -executeRequest:withContext:error: メソッドが渡されたことを認識できるのはストアだけです。

ではNSIncrementalStoreNSIncrementalStoreNodes はストアのスナップショットを表します。ノードのversionプロパティは楽観的ロック プリミティブです。永続ストアは、楽観的ロックの失敗をストア レベルで検出する役割を担いますが、管理対象オブジェクト コンテキストはより高いレベルでそれらを検出できます。ストアが通信しているシステムが他の何かによって変更され、そのシステムの状態と永続ストア内の状態の表現との間に競合がある場合、ストア レベルでの楽観的ロックの失敗が発生する可能性があります。たとえば、ストアが Web サービスと通信していて、Web サービスのデータが別のユーザーによって変更された場合などです。

保存中にストアの実装で楽観的ロックの失敗が検出された場合、ストアはNSMergeConflictそれを説明するオブジェクトを作成する責任があります。これらは によって伝播されますNSPersistentStoreCoordinator

[[NSMergeConflict alloc] initWithSource:managedObject newVersion:newVersion oldVersion:oldVersion cachedSnapshot:inMemorySnapshot persistedSnapshot:storedSnapshot];

スナップショット ディクショナリには、モデル化されたすべての属性プロパティ名をキーとして、その値とともに含める必要があります。これには関係は含まれません。一部のストアでは、参照オブジェクトまたは NSIncrementalStoreNodes からの値を使用しても、モデル化された属性プロパティ名がキーとして含まれているだけで十分な場合があります (エンティティの説明から簡単に取得できます)。

これらのオブジェクトが作成されたら、コードNSErrorでを作成します。userInfo オブジェクトには、オブジェクトの配列を含むキーが含まれている必要があります。保存リクエストからそれを返すと、解決策を見つける責任があります。ストア内のオブジェクトの状態とストア内のオブジェクトの状態との間の競合に対してマージ競合を生成しないでください。ストア内のメモリ内またはキャッシュされた状態と、データが保持または永続化される場所 (Web サービスなど) の間の競合に対してのみ生成する必要があります。またはデータベースなど)NSCocoaErrorDomainNSPersistentStoreSaveConflictsErrorNSPersistentStoreSaveConflictsErrorKeyNSMergeConflictNSPersistentStoreCoordinatorNSManagedObjectContext

于 2014-09-24T03:35:20.030 に答える