May 5, 2017

Core Data unique constraints

There's a bug with Core Data such that if you have two entities, A and B, with a one-to-one relationship between them, that relationship will not be setup properly. This bug doesn't care wether you do your work in Objective-C or Swift or both.

The steps to reproduce are as follows:

  1. Add a one-to-one relationship between the two.
  2. Generate the class file for each by any method you wish. Doesn't matter which.
  3. In your app, create an instance of each and link them by their relationship property.
        let context = persistentContainer.viewContext
        
        let a = A(context: context)
        a.name = "I'm alive"
        let b = B(context: context)
        b.name = "I'm also alive"
        a.thing = b
        
        try! context.save()
        let context = container.viewContext
        
        let request = A.fetchRequest() as NSFetchRequest<A>
        let allA = try! context.fetch(request)
        print(allA.map({ $0.thing?.name }))
        let bRequest = B.fetchRequest() as NSFetchRequest<B>
        let allB = try! context.fetch(bRequest)
        print(allB.map({ $0.otherThing?.name }))
  1. Every time you run this code, you'll see more items in the output.
  2. To prevent all these extra duplicates, let's give them a unique constraint, to both the A and B entities. For this to work, you'll also need to change the merge policy of the context.
context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
  1. To make things work again, remove the unique constraint on B. Now everything works just fine. Tada!

There's an example project displaying this bug on github.