Core Data unique attributes

Viewed 23901

Is it possible to make a Core Data attribute unique, i.e. no two MyEntity objects can have the same myAttribute?

I know how to enforce this programatically, but I'm hoping there's a way to do it using the graphical Data Model editor in xcode.

I'm using the iPhone 3.1.2 SDK.

7 Answers

Every time i create on object I perform a class method that makes a new Entity only when another one does not exist.

+ (TZUser *)userWithUniqueUserId:(NSString *)uniqueUserId inManagedObjectContext:(NSManagedObjectContext *)context
{
    TZUser *user = nil;
    NSFetchRequest *request = [[NSFetchRequest alloc] init];

    request.entity = [NSEntityDescription entityForName:@"TZUser" inManagedObjectContext:context];
    request.predicate = [NSPredicate predicateWithFormat:@"objectId = %@", uniqueUserId];
    NSError *executeFetchError = nil;
    user = [[context executeFetchRequest:request error:&executeFetchError] lastObject];

    if (executeFetchError) {
         NSLog(@"[%@, %@] error looking up user with id: %i with error: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [uniqueUserId intValue], [executeFetchError localizedDescription]);
    } else if (!user) {
        user = [NSEntityDescription insertNewObjectForEntityForName:@"TZUser" 
                                             inManagedObjectContext:context];
    }

    return user;
}

I've decided to use the validate<key>:error: method to check if there is already a Managed Object with the specific value of <key>. An error is raised if this is the case.

For example:

- (BOOL)validateMyAttribute:(id *)value error:(NSError **)error {
    // Return NO if there is already an object with a myAtribute of value
}

Thanks to Martin Cote for his input.

You could override the setMyAttribute method (using categories) and ensure uniqueness right there, although this may be expensive:

- (void)setMyAttribute:(id)value
{
   NSArray *objects = [self fetchObjectsWithMyValueEqualTo:value];
   if( [objects count] > 0 )  // ... throw some exception
   [self setValue:value forKey:@"myAttribute"];
}

If you want to make sure that every MyEntity instance has a distinct myAttribute value, you can use the objectID of the NSManagedObject objects for that matter.

You just have to check for an existing one :/

I just see nothing that core data really offers that helps with this. The constraints feature, as well as being broken, doesn't really do the job. In all real-world circumstances you simply need to, of course, check if one is there already and if so use that one (say, as the relation field of another item, of course). I just can't see any other approach.

To save anyone typing...

// you've download 100 new guys from the endpoint, and unwrapped the json
for guy in guys {
    // guy.id uniquely identifies
    let g = guy.id
    
    let r = NSFetchRequest<NSFetchRequestResult>(entityName: "CD_Guy")
    r.predicate = NSPredicate(format: "id == %d", g)
    
    var found: [CD_Guy] = []
    do {
        let f = try core.container.viewContext.fetch(r) as! [CD_Guy]
        if f.count > 0 { continue } // that's it. it exists already
    }
    catch {
        print("basic db error. example, you had = instead of == in the pred above")
        continue
    }
    
    CD_Guy.make(from: guy) // just populate the CD_Guy
    save here: core.saveContext()
}
or save here: core.saveContext()

core is just your singleton, whatever holding your context and other stuff.

Note that in the example you can saveContext either each time there's a new one added, or, all at once afterwards.

(I find tables/collections draw so fast, in conjunction with CD, it's really irrelevant.)

(Don't forget about .privateQueueConcurrencyType )

Do note that this example DOES NOT show that you, basically, create the entity and write on another context, and you must use .privateQueueConcurrencyType You can't use the same context as your tables/collections .. the .viewContext .

let pmoc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
pmoc.parent = core.container.viewContext
do { try pmoc.save() } catch { fatalError("doh \(error)")}
Related