CoreStore insert array as relationship objects while updating data model

652 Views Asked by At

I have next code to update my workout:

func addMuscleGroups(muscleGroups: [MusclEntity], toWorkout: WorkoutEntity, completion: @escaping (ResultInfo<WorkoutEntity>) -> Void) {

        CoreStore.perform(
            asynchronous: { (transaction) -> WorkoutEntity? in
                let workout = transaction.edit(toWorkout)!

                for muscle in muscleGroups {
                    let editedMuscle = transaction.edit(muscle)!
                    workout.muscles?.insert(editedMuscle)
                }

                return workout
        },

            success: { (transactionWorkout) in // here we have muscles for transactionWorkout

                guard let unwrappedTransactionWorkout = transactionWorkout else {
                    return
                }

                let workout = CoreStore.fetchExisting(unwrappedTransactionWorkout) // there are no muscles objects

                guard let unwrappedWorkout = workout else {
                    return
                }

                completion(.data(unwrappedWorkout))
        },
            failure: { (error) in
                completion(.error(StackedError.internalFetchingDataError(message: error.debugDescription)))
        })

    }

But as I see I have inserted muscles while perform asynchronous block, but then when I do fetchExisting there are no muscles which were added in asynchronous block.

EDITED:

As I figured out there is missing relationships inverse in workout.muscles, now I added it and looks like CoreStore update workout entity correctly.

But now I have another problem if I call function few times:

  1. first call

addMuscleGroups([1, 2, 3, 4, 5]...) print(unwrappedWorkout.muscles) [1, 2, 3, 4, 5]

  1. second call addMuscleGroups([1, 2, 3, 4, 5, 6, 7]...)

    print(unwrappedWorkout.muscles) [6, 7]

(to simplify example I used 1, 2, 3, 4, 5, 6, 7 numbers), but there are MusclEntity objects actually

so as a result of second call of addMuscleGroups function unwrappedWorkout.muscles has two muscle objects instead of 7.

I've added short video:

https://www.screencast.com/t/63cRGKpk0Q

2

There are 2 best solutions below

4
John Estropia On BEST ANSWER

I answered your question at CoreStore's github as well.

This would be the general behavior of NSManagedObjects with regards to to-many properties.

First, as you have found out, the inverse relationship is important.

Second, Set is not recommended for unordered @NSManaged properties. Using NSSet is recommended instead. In that note, to-many relationships are treated as immutable, which means you cannot insert objects directly to the NSSet value. There are two ways you can insert values.

  1. Assigning a new NSSet directly:
var muscles = (workout.muscles as! Set<MusclEntity>?) ?? []
muscles.insert(newMuscle)
workout.muscles = muscles as NSSet
  1. Use KVO mutable accessors (creates an NSMutableSet proxy for you):
workout.mutableSetValueForKey(#keyPath(WorkoutEntity.muscles)) 
    .addObject(newMuscle)
0
Oleksandr Matrosov On

I have PlaylistEntity and this is how my import looks like. This func is called when I do transaction.importUniqueObject for my playlist, what I want to do here is to import all needed properties of playlist and relationships (in this simple example called songs):

public func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
        self.id = id
        self.title = source["title"] as? String

        if let songsJsonArray = source["songs"] as? [[String: Any]] {
            let songs = try! transaction.importUniqueObjects(
                Into<SongEntity>(),
                sourceArray: songsJsonArray)

            for song in songs {
                self.mutableSetValue(forKey: #keyPath(Playlist.songs))
                    .add(song) // This code will insert new song to NSSet which is unique and can't be duplicated. 100% working code
            }

            // this code:
            // self.songs = NSSet(array: days)
            // actually will not work well for all cases I will explain below
        }
    } 

Let's say you want to add few songs to playlist and you've called this function which update you local storage with new song by adding it to playlist and make request to the server:

inserNewSong(song1, toPlaylistWithId: someId_abc1)
inserNewSong(song2 toPlaylistWithId: someId_abc1)
inserNewSong(song3 toPlaylistWithId: someId_abc1)

So now your local storage has 3 songs in playlist. But we sill waiting for response form the server.

So each time songs is added on the server and server replied back, I reimport playlist using update func above.

The first response from the sever will return only one song in array, but as I mentioned my local storage already has 3 songs but this code self.day = NSSet(array: days) will replace 3 songs with just one.

Second response will return [song1, song2], and this code self.day = NSSet(array: days) will replace 3 songs with just 2.

For sure if you have quick connection and all 3 requests the the server finished immediately, you've probably will not see this effect, though I would like to use second approach suggested by @JohnEstropia:

self.mutableSetValue(forKey: #keyPath(Playlist.songs))
                        .add(song)