Core data - Set many-to-one relationship with NSBatchInsertRequest

Viewed 1175

I have two entities, User and Post. A User contains many Post, and each Post has only one User.

I am trying to batch insert more than 1000 posts.

private func newBatchInsertRequest(with posts: [PostData]) -> NSBatchInsertRequest {
    var index = 0
    let total = posts.count
    let batchInsert = NSBatchInsertRequest(entity: Post.entity()) { (managedObject: NSManagedObject) -> Bool in
        guard index < total else { return true }
        
        if let post = managedObject as? Post {
            let data = posts[index]
            post.createdData = data.createdDate
            post.identifier = data.identifier
            post.text = data.text
        }
        
        index += 1
        return false
    }
    
    PersistenceController.shared.container.performBackgroundTask { context in
        try? context.execute(batchInsert)
        try? context.save()
    }
}

It inserts all the posts that I want to insert. However, I can not configure how to set their User.

I tried to use the following code, but it did not work.

let updateRequest = NSBatchUpdateRequest(entity: Post.entity())
updateRequest.resultType = .updatedObjectIDsResultType
updateRequest.propertiesToUpdate = ["user": user]

 try? context.execute(updateRequest) 

I get the following error.

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid relationship ((<NSRelationshipDescription: 0x2801045a0>), name item, isOptional 1, isTransient 0, entity Post, renamingIdentifier item

I can set their user one by one, but it is inefficient due to long processing time.

How to update the user property of Posts in a more efficient way?


Update

All of these posts belongs to one User which does not exist yet. I need to create it before or after executing the NSBatchInsertRequest.

User has three properties

   1. createdDate: Date
   2. identifier: UUID
   3. name: String

My goal is to insert Post that belongs to one Use either using NSBatchInsertRequest or in a private context so that it does not block the main thread.

2 Answers

Here's what I would try:

  • Create the User before your batch operation. Yep, it's going to be on a different context — just make sure you grab its NSManagedObjectID (it's a property on NSManagedObject) and save it on a private property.
  • Inside your PersistenceController.shared.container.performBackgroundTask you have a reference to your private context. Use the managed object ID from before to fetch/register the same User in your private context:
let user = context.object(with: userManagedObjectID)
  • Then pass this user into the method that creates your batch insert request to set the user property on your Post objects.

I believe if you set up your inverse relationship up in the data model editor you don't need to populate User.posts. That should happen automatically for you.

I solved this problem in different way.

  1. In my entity which needs to have relationship added placeholder attributed tmpID.
  2. When creating batch inserts setting tmpID to particular string. As example parent objectID parent.objectID.
  3. Executing batch insert.
  4. Then fetching all object from CoreData with tmpID.
  5. Then going throw everyone and setting:
    1. Relationship
    2. tmpID to nil, the do not waist a memory.
  6. Saving managed object context.
Related