6

このコードは、「CoreData:エラー:(19)PRIMARYKEYは一意である必要があります」エラーを発生させます。Dayエンティティには、である属性と、と呼ばれる多対数の関係のみがwhenありNSDateますtasks。なぜこのエラーですか?特定の日付のがすでに保存されている場合Dayはそれをフェッチし、そうでない場合は挿入します。したがって、日オブジェクトごとに、異なるwhen属性が必要です。ただし、これが主キーかどうかはわかりません。これを解決する方法は?前もって感謝します。

   NSMutableSet *occurrences = nil;

   occurrences = ... 
   NSMutableOrderedSet *newSet = [NSMutableOrderedSet orderedSetWithCapacity:[occurrences count]];

   for(NSDate *current in occurrences) {

        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        // try to find a corresponding Day entity whose when attribute is equal to the current occurrence
        // if none is available, create it

        Day * day = [[self getDayForDate:current inManagedObjectContext:moc] retain];
        if(!day){
            day = (Day *) [NSEntityDescription insertNewObjectForEntityForName:@"Day" inManagedObjectContext:moc];
        }

        day.when = current;
        [day addTasksObject:aTask];

        [newSet addObject:day];

        [moc insertObject:day];
        [moc processPendingChanges];

        [day release];

        [pool release];            
    } 

- (Day *)getDayForDate:(NSDate *)aDate inManagedObjectContext:(NSManagedObjectContext *)moc
{        
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Day" inManagedObjectContext:moc];
    [request setEntity:entity];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(when == %@)", aDate];
    [request setPredicate:predicate];
    NSError *error = nil;
    NSArray *array = [moc executeFetchRequest:request error:&error];
    [request release];

    Day *theDay = nil;

    if(array && [array count] == 1){
        theDay = [array objectAtIndex:0];
    }

    return theDay;
}
4

2 に答える 2

19

私は最近、CoreData: error: (19) PRIMARY KEY must be unique社内のiOSアプリケーションでエラーに苦しんでおり、1、2日の調査とコード変更の後で解決策を見つけました。その結果が役立つことを願って、ここで私の発見を共有したいと思いました。その他。

まず、少し背景

社内アプリは元々、CoreDataストアを更新するためのコードでビルドされたことはありませんでした-代わりに、アプリ内に新しいデータを表示する必要がある場合のアプリビルドの反復ごとに、CoreDataSQLiteバッキングストアファイルは単に新しいバージョンに置き換えられました標準のデスクトップベースのSQLiteエディターで編集されていました。つまり、生のSQLiteファイルが必要に応じて変更および更新されていました。このアプローチはCoreDataの「ブラックボックス」の性質を考慮していないことが認識されていますが、実際には過去数年間、シンプルなアプリでうまく機能し、アプリは更新されたSQLiteファイルをそれぞれで喜んで受け入れていました。新しいアプリのビルド。

ただし、最近、アプリの大幅な見直しを行い、Xcodeとアプリの再構築を介してアプリのアセットとデータを更新するモデルから、代わりにWebサービスを介して新しいアプリのデータを取得するモデルに移行しました。これは、この新しい開発中のことでした。PRIMARY KEY must be unique問題が発生した作業。

問題の解決に数日間苦労した後、新しいCoreDataエンティティ作成コード(徹底的にチェックおよび再チェックされた)またはその他の関連する問題に何らかの障害があるはずだと考え、さらに調査した結果、次のことが可能であることがわかりました。 Xcodeを使用してアプリのCoreDataSQLデバッグを有効にします(この非常に役立つSO投稿の手順に従って)。

XcodeでSQLログを注意深く調べたところ、CoreDataがINSERTSQLiteバッキングストアへの新しいレコードを呼び出すたびに、フレームワークが次のクエリでZ_PRIMARYKEYテーブルをクエリし、バックグラウンドで関連する値に置き換えられていることがわかりました。関連するCoreDataエンティティの場合(テーブルの内容を確認することで、各CoreDataエンティティの値を確認できます)。これは、CoreDataのブラックボックス内で何が起こっているのかをようやく理解したときです。次に、MacでLiyaアプリを使用してアプリのSQLiteファイルを詳しく調べ、テーブルの内容を確認して、列の値がすべて次のように設定されていることを確認しました。SELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ??Z_ENTZ_ENTZ_PRIMARYKEYZ_PRIMARYKEYZ_MAX0。CoreDataがアプリ用の空のSQLiteバッキングストアファイルを最初に生成したときの初期値から変更されたことはありませんでした。

何が問題になっているのか、そしてCoreDataが主キーエラーを報告した理由をすぐに理解しました。実際には、最初に疑われたように、高レベルのCoreDataエンティティオブジェクトの属性が何らかの形で衝突することとは関係ありませんでしたが、実際には低レベルでした。エラー。エラーが絶対的に意味をなすのは今だけで、それはずっとそこにあり、正しい文脈の中で理解されていなかっただけです。

さらなる調査の結果、社内チームは過去数年間、SQLiteバッキングストアを直接編集し、さまざまなエンティティテーブルからレコードを挿入、更新、削除してきましたが、私たちのチームは変更を加えていませんでした。テーブルにZ_PRIMARYKEY、そして私たちのアプリが読み取り専用の方法でCoreDataを利用していた方法のために、これは決して問題ではありませんでした。

ただし、アプリがCoreDataエントリを作成してSQLiteバッキングストアに保存しようとしたため、INSERTCoreDataが生成元の正しい最大主キー値を取得できなかったため、結果として生成されていたクエリの多くが失敗しました。任意のテーブルの次のシーケンシャル主キー。そのため、INSERTクエリは失敗し、今では理解できる意味のあるPRIMARY KEY must be uniqueエラーが発生します。

エラーの解決

各開発者はさまざまな方法でアプリのデフォルト/初期CoreDataコンテンツを生成する可能性があることを認識していますが、CoreDataでこの特定のエラーに遭遇し、すぐに明確な答えを見つけるのに苦労したことがある場合は、次の考えを願っています助けになります:

  • 私たちのアプリのCoreDataバッキングストアは、SQLiteファイルを直接編集する(エンティティテーブルからのレコードの挿入、更新、削除)ことで手動で更新されました-このアプローチは、このデータを読み取り専用の方法で使用していたため、長い間機能していました私たちのアプリ。ただし、このアプローチでは、CoreDataの抽象的な「ブラックボックス」の性質と、SQLiteレコードがCoreDataエンティティと同等ではないという事実、およびSQLite結合がCoreData関係と同等ではないという事実などを真に認識することができませんでした。

  • アプリが事前に入力されたCoreDataストアを使用している場合、およびCoreData以外の方法でSQLiteバッキングストアのコンテンツを入力している場合は、CoreDataエンティティテーブルのいずれかに新しいレコードを作成する場合は必ず確認してください。 、また、テーブル内の関連するレコードを更新し、列の値を関連するエンティティテーブル内の現在の最大値にZ_PRIMARYKEY設定することも確認してください。Z_MAXZ_PK

    たとえば、と呼ばれるCoreDataエンティティがある場合Employee、CoreData SQLite永続ストアバッキングファイル内で、これは次の名前のテーブルで表されます-このテーブルには、 を表す列に加えて、、、ZEMPLOYEEなどを含むいくつかの「非表示」CoreData列がありますエンティティの属性と関係。このエンティティテーブルには、値が次の対応するレコードもあります。したがって、新しいレコードをテーブルに直接追加する場合は、レコードの追加が完了したら、各エンティティテーブルを調べて、最大値をコピーするようにしてください。テーブルの列の値。入力する値は最大である必要がありますZ_PKZ_ENTZ_OPTZ_PRIMARYKEYZ_NAMEEmployeeZEMPLOYEEZ_PKZ_MAXZ_PRIMARYKEYZ_MAXZ_PK対応するテーブルの値。Z_MAX+ 1の値と等しくなるように設定しないでくださいZ_PK。これは、CoreDataが期待するものではないためです。

Z_PK次のSQLを使用して、任意のエンティティテーブルの最大値をクエリできます。

"SELECT Z_PK FROM ZEMPLOYEE ORDER BY Z_PK DESC LIMIT 1"

Z_PKこれにより、テーブル内のすべてのレコードに最大の値が与えられます。明らかに、アプリのデータモデルに関連するテーブル名にZEMPLOYEE置き換える必要があります。ZEMPLOYEE

私たちのアプリでは、SQLiteファイルを直接読み取り、Z_PRIMARYKEYテーブルの各レコードを反復処理して正しいZ_MAX値で更新する簡単なコマンドラインスクリプトを作成することができました。これが完了すると、この更新されたファイルをアプリの永続ストアバッキングファイルとして使用できるようになりました。これで、新しいCoreDataエンティティを挿入して保存すると、すべてが期待どおりに機能します。

アプリがWebサービスを介して新しいデータを直接リクエストする方法を備えたので、SQLiteバッキングストアを手動で編集することから完全に移行し、CoreDataフレームワークのみを使用してデータを更新します。このような迅速で簡単なデータ入力が必要になる可能性があります。この回答が他の開発者の助けになり、このソリューションを見つけるのにかかった時間と労力を節約できることを願っています。

于 2013-02-04T22:38:43.653 に答える
7

すでに持っている場合は、新しいものを挿入する必要はないと思いますDay(これは日がゼロでない場合です)。特に私が言及しているのは[moc insertObject:day]

を使用する場合insertNewObjectForEntityForName、そのメソッドは、mocを保存するときにオブジェクトを挿入します。変更する必要がある場合(nil以外の日を取得した場合)、変更して保存します。さらにprocessPendingChanges、ループが終了したときに実行します(パフォーマンス上の理由から)。

お役に立てば幸いです。

于 2012-09-19T12:17:46.667 に答える