UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Optional CoreData objects duplication

Forums > SwiftUI

In my app, I have a CoreData entity "Work", for music classical pieces, which has an "instruments" related property. It's a Many - ToMany relationship, as one work can bre written for many instruments, as an instrument can be used on many works.

I present on one of my app views the list of instruments that work is written for as follows:

Section("Instrumentation") {
  if work.instruments?.count ?? 0 > 0 {
    ForEach(Array(work.instruments as! Set<Instrument>)) { instrument in
      Label(instrument.name ?? "", systemImage: "guitars")
             ...

And add instruments to this list on another view, summing up the selected ones from CoreData on an Instrument array, with:

func saveInstruments(instrumentsToBeAdded: [Instrument]) {
    let moc = self.managedObjectContext
    let set = NSSet(array: instrumentsToBeAdded)
    self.addToInstruments(set)
    try? moc?.save()
  }

Now, my problem is that in some cases I need to include to this Set of Instrument for one Work object more than one repeated Instrument. For example: string quartets have two "Violin", and I don't want to create two "Violin" instruments on its own.

I think (I'm not sure) that this behaviour is controlled on my DataController class whith:

self.container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump

And, in fact, is very useful for all of the rest on my app entities to avoid duplicates. But, how can I allow this precise object duplicates, only for this entity and relationship?

Thank you very much in advance.

1      

hi Óscar,

just for clarification: are you requiring that there be only one instance of a Violin object in your Core Data object graph, yet you'd like allow multiple associations of it to same Work?

(BTW: the mergePolicy is only relevant when you use NSPersistentCloudKitContainer or have multiple contexts in play; i donlt think it has anything to do with the question at hand.)

i have an idea; it just depends where you want to go.

DMG

1      

Thanks, DMG.

Yes, exactly that: only one Violin, one Cello, one Piano... on my CoreData "Instrument" entity. And correct: I'd like to have repeated "mentions" of the same related "Work" entity:

Instruments:

  • Violin
  • Viola
  • Cello
  • Piano
  • Clarinet
  • Flute
  • ...

Works:

  • Sonata (Violin + Piano)
  • Trio (Violin + Cello + Piano)
  • Quartet (Violin + Violin + Viola + Cello)
  • ...

When I add a second "violin" object, it "merges" automatically, and only one appears, and saves.

1      

You may consider augmenting your model with the instrument's Part, or Chair.

For example, a piece will have a part for a Violin I and a Violin II.

I think this might model an actual composition, or score. Changing your model to reflect the instrument's Chair (first Chair?) or Part may better represent real-world compositions.

1      

hi Óscar,

so we agree on your plan, but to implement it, my suggestion is to use some intermediate indirection.

first, when you associate a Work with an Instrument, you don't have any detail regarding this association. so your Core Data model is basically:

  • Work << --- >> Instrument // the double arrows indicate "to many"

where can you store, say, the number of violins (Instruments) associated with this string quartet (Work)? unfortunately, you can't do it in either of the Work or Instrument entities (because these objects have plenty of associations with other objects throughout the Core Data graph).

one answer i have used is to place an intermediate object in the relationship, that looks like this

  • Work < --- >> InstrumentDescriptor << --- > Instrument

where the single arrow indicates "to one", and the InstrumentDescriptor object takes on the role of being an object that describes a relationship of one Work with one Instrument, but also maintains the number of times the association should be understood.

a class pseudo-definition for InstrumentDescriptor, sans the @NSManaged and Objective-C syntax, would be this:

class InstrumentDescriptor {
  var work: Work
  var instrument: Instrument
  var count: Int
}

(a) how do you associate an Instrument with a Work, if these were never associated before?

  • create a new InstrumentDescriptor
  • link the InstrumentDescriptor to the Work
  • link the InstrumentDescriptor to the Instrument
  • increment the InstrumentDescriptor's count (which should be zero by default)

(b) but what if i already have an association of this Instrument with the Work?

  • locate whichever of the InstrumentDescriptors associated with the Work references the Instrument
  • if found, increment its count
  • otherwise, follow out part (a)

(c) how do i find which Instruments are associated with a given Work?

extension Work {
  func associatedInstruments() -> [Instrument] {
    // get all the instrument descriptors associated with the work
    let descriptors = (self.instrumentDescriptors as Set<InstrumentDescriptor>) ?? []
    // condense all the related Instruments into a Set
    return descriptors.map({ $0.instrument })
  }
}

(d) can i get a list of Instruments associated with a given Work and how many times they are used?

extension Work {
  func instrumentReport() -> String {
    // get all the instrument descriptors associated with the work
    let descriptors = (self.instrumentDescriptors as Set<InstrumentDescriptor>) ?? []
    // report Instruments and their counts
    var report = ""
    for descriptor in descriptors {
      report += "\(descriptor.instrument.name) used \(descriptor.count) times"
    }
    return report
  }
}

and on it goes.

i'm not a database-guy, as such, and it takes a little time to wrap your head around what i think is called a join record, but i have used this idea before and it works.

in short, whenever you need more data about a relatonship between A and B, such as "why or how exactly is A related to B," you can add such an intermediate record to represent the relationship, as well as store the why or how exactly information.

hope that helps,

DMG

(disclaimer ... this is sort of seat-of-the-pants coding; i'll review this shortly once i go back to read it ... i think it's OK now.)

1      

Wow! Thank you so much, DMG, for your extensive explanation. I'll try your approach...

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.