Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CoreData creates and saves extra entities with nil attributes

I have a Contact table and Location table with many locations for each contact

I create a Contact save and then create a Location and save and then assign the saved Location to the Contact. Code below:

 @IBAction func saveLocation(_ sender: AnyObject) {
    let processName = ProcessInfo.processInfo.globallyUniqueString

    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    let location = Location(context: context)

    do {

        let fetchRequest: NSFetchRequest<Contact> = Contact.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "uniqueId == %@", contactIdentifierString)
        let fetchedResults = try context.fetch(fetchRequest)
        if let aContact = fetchedResults.first {

            // set location data
            location.uniqueId = processName
            location.locationName = locationNameTextField.text
            location.city = self.city
            location.state = self.state
            location.street = self.street
            location.zip = self.zip
            location.country = self.country


            // save data
            (UIApplication.shared.delegate as! AppDelegate).saveContext()
            location.contact = aContact
            myDelegate?.userSelectedContact(contactIdentifier: contactIdentifierString, locationIdentifier: processName)

        }
    }
    catch {
        print ("fetch task failed", error)
    }




    // Check results
    do {

        let fetchRequest: NSFetchRequest<Location> = Location.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "uniqueId == %@", processName)
        let fetchedResults = try context.fetch(fetchRequest)
        if let aLocation = fetchedResults.first {
            print("+++++==========++++++++++")
            print("Location.Contact:  \(aLocation.contact)")
            print("+++++==========++++++++++")
        }
    }
    catch {
        print ("location fetch failed")
    }

    self.navigationController!.popViewController(animated: true)
}

I added just one Location, however when I print the contact entity I see two locations assigned as you can see below.

  Location.Contact:  Optional(<rg2nrg.Contact: 0x6000002c73f0> (entity: Contact; id: 0xd000000000240000 <x-coredata://7D46FA65-2590-409D-89E7-995F64F07483/Contact/p9> ; data: {
email = vmail;
firstName = vr;
imageName = <89504e47 0d0a1a0a 0000000d 49484452 0000012c 0000012c 08060000 00797d8e 75000000 01735247 4200aece 1ce90000 0009>;
lastName = r;
locations =     (
    "0xd000000001780002 <x-coredata://7D46FA65-2590-409D-89E7-995F64F07483/Location/p94>",
    "0xd000000001740002 <x-coredata://7D46FA65-2590-409D-89E7-995F64F07483/Location/p93>"
);
phone = 22;
providerName = tara;
screenName = vr;
uniqueId = "7B17C228-50A6-4309-BD0D-CBCD8E8FAEA1-1106-00000128477D4DB1";
userType = Household;
  }))

Further, one of location entities has the correct the values of assigned attributes where as the other location entity has all nil values.

This messes up my program. As I go over location entity attributes in a loop to delete them I now see three location entities

 func deleteLocations() {
    var fetchedResults: [Location] = []

    let contextLocation = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    do {

        fetchedResults = try contextLocation.fetch(Location.fetchRequest())
        print("number of locations to be deleted:: \(fetchedResults.count) ")

        if fetchedResults.count > 0 {

            for location in fetchedResults{
                print(location.locationName)
                contextLocation.delete(location)
                try contextLocation.save()

                print(location.locationName)
            }
        }
    } catch {
        print ("fetch contact failed")
    }

}


Output: number of locations to be deleted:: 3

In the later part of my code when I try to retrieve values it crashes because all the attributes of the two extra Location entities is all nil.

My question: Because I added only one Location entity, there should only be one Location entity and that should be the only one linked to Contact. It shows two Locations assigned to Contact and then when I get count I have three Locations. Looks like Location entities with nil attributes keep getting added on its own.

I am confused. Can someone help?

like image 499
vrao Avatar asked Mar 21 '17 23:03

vrao


People also ask

What is entity in Core Data?

An entity description describes an entity (which you can think of as a table in a database) in terms of its name, the name of the class used to represent the entity in your application, and what properties (attributes and relationships) it has.

How do I save an object in Core Data?

To save an object with Core Data, you can simply create a new instance of the NSManagedObject subclass and save the managed context. In the code above, we've created a new Person instance and saved it locally using Core Data.

What is transformable Core Data?

Core Data allows us to store integers, booleans, strings, UUID, date, etc. but sometimes we want to store a specific data type like UIColor, UIImage, our own class, struct, or enum, and even arrays, but that is simply not an option in Attribute's Type.

What is entity in Core Data Swift?

An entity describes an object, including its name, attributes, and relationships. Create an entity for each of your app's objects.


1 Answers

You are most probably calling saveLocation in some situations where either the fetch fails or no results match the predicate, and saveLocation does not properly handle these situations.

init(context:)

This initializer on NSManagedObject not only creates an empty Location object in memory when you call Location(context: context), but it also inserts that object into the managed object context you passed. Once saveContext() is called, that Location object (which will be empty if it is not further edited) will be persisted.

While init(context:) is not well-documented, Apple seems to have indicated that it works the same as init(entity:insertInto:) except that the entity is automatically figured out.

saveLocation(:)

In this function, you have created your Location object…

let location = Location(context: context)

…before you check to see if the Contact exists and set the properties.

if let aContact = fetchedResults.first

Therefore, if it so happens that the fetch fails or the fetch comes back with 0 results, the empty Location object created when you called the initializer is left in the context, to be persisted the next time anyone calls saveContext().

Solution and Prevention

In this particular case, you will probably find that your issue will be solved after moving this line

let location = Location(context: context)

to the if let block below:

if let aContact = fetchedResults.first {

    // create location here, not at beginning of method
    let location = Location(context: context)

    // set location data
    location.uniqueId = processName
    location.locationName = locationNameTextField.text
    location.city = self.city
    location.state = self.state
    location.street = self.street
    location.zip = self.zip
    location.country = self.country

    // save data
    (UIApplication.shared.delegate as! AppDelegate).saveContext()
    location.contact = aContact
    myDelegate?.userSelectedContact(contactIdentifier: contactIdentifierString, locationIdentifier: processName)

}

As a general rule that will prevent this issue when using Core Data, don't use init(context:) on any NSManagedObject subclass unless you have first performed all necessary checks and are sure you want the item inserted into the context immediately.

like image 141
Matthew Seaman Avatar answered Oct 20 '22 18:10

Matthew Seaman