CoreDataのentityに擬似的な一意キー項目を追加する

| コメント(0) | トラックバック(0)
こんにちは。開発担当のICTFです。

RDBのつもりでCoreDataを使い始めたのですが、全然別物でした。
まだ勉強を初めて間もないですが、今のところ「本当に便利なのかな?」というのが正直な感想です。
しかし、とりあえずはCoreDataに関する知識を深めたいという事もあり、現在開発中のアプリにCoreDataを用いる事にしました。

アプリ内では複数のentity(RDBで言うところのtableに相当します)を用いるのですが、entity間の連結をどう表現するか。という部分の解決に随分苦労しました。
RDBであればautoNumberタイプの項目を主キーとして、別テーブルの外部キーに設定したいところなのですが、CoreDataには主キーという概念もautoNumber型の変数も存在しません。
かといってuserDefaultに主キーの最大値を保存しておき、レコード追加時にそこから値を読み取って使用する・・・というのも格好が悪い気がします。

理想はレコード追加時に意識せずとも正しく一意な値が「自動で」記録される事です。
今回はそれを目指して実装してみました。
CoreDataの仕組みのみで動作し、外部の機能(userDefaultに値を保存するなど)にも頼らないので汎用性は高いかと思います。

前提として、
・使用するentityは「Template」とする
・Template entity内のAttibuteは以下の通り
 ⇒templateID : Integer 64
 ⇒createDate : Date
 ⇒title : String
となります。
templateIDが今回の首題となる一意キー項目です。

まず上記entityのNSManageObjectサブクラスを作ります。
サブクラス名はentity名と同じ「Template」です。
一意値の取得と設定はこのTemplateクラス内に実装します。

Template.mにawakeFromInsertメソッドを追加します。
このメソッドはレコードが新規に追加される際に呼び出されるメソッドです。
awakeFromInsert内に一意値の取得し、それを初期値として設定する為の処理を記述します。

-(void) awakeFromInsert

{

[super awakeFromInsert];

self.createDate = [NSDate date];

self.title = @"";

// 一意キーを設定する

NSManagedObjectContext* managedObjectContext = [CommonFunctions getAppDelegate].managedObjectContext;

NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];

NSEntityDescription* entity = [NSEntityDescription entityForName:@"Template" inManagedObjectContext:managedObjectContext];

[request setEntity:entity];

[request setResultType:NSDictionaryResultType];

NSExpression* keyPathExpression = [NSExpression expressionForKeyPath:@"templateID"];

NSExpression* maxIDExpression = [NSExpression expressionForFunction:@"max:" arguments:[NSArray arrayWithObject:keyPathExpression]];

NSExpressionDescription* expressionDescription = [[[NSExpressionDescription alloc] init] autorelease];

expressionDescription.name = @"maxID";

expressionDescription.expression = maxIDExpression;

expressionDescription.expressionResultType = NSDecimalAttributeType;

[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];

NSError* error = nil;

NSArray* objects = [managedObjectContext executeFetchRequest:request error:&error];

if (objects == nil) {

// エラー

NSLog(@"TemplateID get Error");

self.templateID = [NSNumber numberWithInteger:0];

}

else {

NSInteger initID = 0;

if (objects.count > 0) {

NSDecimalNumber* maxID = [[objects objectAtIndex:0] valueForKey:@"maxID"];

NSLog(@"maxID: %@", maxID);

initID = [maxID integerValue] + 1;

}

NSLog(@"new Template ID: %d", initID);

self.templateID = [NSNumber numberWithInteger:initID];

}

}


Template entity内のレコードからtemplateIDの最大値をフェッチし、その値に1を加算したものを一意キーとして設定しています。

以下のケースで正しく動作しない事が懸念されますので、その辺は適宜実装して下さい。
・templateIDの最大値取得失敗時に一意キーとして0を設定している。
 ⇒通常の動作であれば最低値は1となりますので、キー値が0である場合はエラー結果であると断定できます。できますが、実際に処理を行なう箇所にその判定を入れ忘れていたら意味がありません。
・NSInteger型の最大値に達した場合の処理が記述されていない
 ⇒早々無いとは思いますが、必要であれば値のループ処理などを入れた方が安全でしょう。
・コンテキストをsaveする前に次のレコードの追加処理が実行される
 ⇒コンテキストがsaveされない限り、永続dataStoreにはコミットされません。それはつまり、templateIDの最大値取得結果が正しくなくなる事を意味します。
 例)
  templateID最大値取得 > 1
  一意キー値として1+1=2 を設定
  save
  templateID最大値取得 > 2
  一意キー値として2+1=3 を設定  ①
  [save しない]
  templateID最大値取得 > 2
  一意キー値として2+1=3 を設定  ②
  
  ①と②の一意キーが重複し、一意性が失われます。

特に最後の項目の心配をしないといけないケースでは、おとなしく普通のRDBを使った方が楽なのかもしれません。

トラックバック(0)

トラックバックURL: http://www.ict-fractal.com/MovableType/mt/mt-tb.cgi/22

コメントする

Twitterボタン
Twitterブログパーツ