Core Data unique attributes

后端 未结 7 1329
眼角桃花
眼角桃花 2020-12-02 10:01

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

相关标签:
7条回答
  • 2020-12-02 10:33

    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)")}
    
    0 讨论(0)
  • 2020-12-02 10:35

    I really liked @DoozMen approach!! I think it's the easiest way to do what i needed to do.

    This is the way i fitted it into my project:

    The following code cycles while drawing a quite long tableView, saving to DB an object for each table row, and setting various object attributes for each one, like UISwitch states and other things: if the object for the row with a certain tag is not present inside the DB, it creates it.

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    
    request.entity = [NSEntityDescription entityForName:@"Obiettivo" inManagedObjectContext:self.managedObjectContext];
    request.predicate = [NSPredicate predicateWithFormat:@"obiettivoID = %d", obTag];
    NSError *executeFetchError = nil;
    results = [[self.managedObjectContext executeFetchRequest:request error:&executeFetchError] lastObject];
    
    if (executeFetchError) {
        NSLog(@"[%@, %@] error looking up for tag: %i with error: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), obTag, [executeFetchError localizedDescription]);
    } else if (!results) {
    
        if (obbCD == nil) {
        NSEntityDescription *ent = [NSEntityDescription entityForName:@"Obiettivo" inManagedObjectContext:self.managedObjectContext];
        obbCD = [[Obiettivo alloc] initWithEntity:ent insertIntoManagedObjectContext:self.managedObjectContext];
        }
    
        //set the property that has to be unique..
        obbCD.obiettivoID = [NSNumber numberWithUnsignedInt:obTag];
    
        [self.managedObjectContext insertObject:obbCD];
        NSError *saveError = nil;
        [self.managedObjectContext save:&saveError];
    
        NSLog(@"added with ID: %@", obbCD.obiettivoID);
        obbCD = nil;
    }
    
    results = nil;
    
    0 讨论(0)
  • 2020-12-02 10:36

    From IOS 9 there is a new way to handle unique constraints.

    You define the unique attributes in the data model.

    You need to set a managed context merge policy "Merge policy singleton objects that define standard ways to handle conflicts during a save operation" NSErrorMergePolicy is the default,This policy causes a save to fail if there are any merge conflicts.

    - (NSManagedObjectContext *)managedObjectContext {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
        if (_managedObjectContext != nil) {
            return _managedObjectContext;
        }
    
        NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
        if (!coordinator) {
            return nil;
        }
      _managedObjectContext = [[NSManagedObjectContext alloc]    initWithConcurrencyType:NSMainQueueConcurrencyType];
      [_managedObjectContext setPersistentStoreCoordinator:coordinator];
      [_managedObjectContext setMergePolicy:NSOverwriteMergePolicy];
        return _managedObjectContext;
    }
    

    The various option are discussed at Apple Ducumentation Merge Policy

    It is answered nicely here Zachary Orr's Answer

    and he has kindly also created a blogpost and sample code.

    Sample Code

    Blog Post

    The most challenging part is to get the data Model attributes editable.The Secret is to left click and then right click, after you have clicked the + sign to add a constraint.

    0 讨论(0)
  • 2020-12-02 10:36

    Take a look at the Apple documentation for inter-property validation. It describes how you can validate a particular insert or update operation while being able to consult the entire database.

    0 讨论(0)
  • 2020-12-02 10:40

    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;
    }
    
    0 讨论(0)
  • 2020-12-02 10:43

    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.

    0 讨论(0)
提交回复
热议问题