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?
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.
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.
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.
An entity describes an object, including its name, attributes, and relationships. Create an entity for each of your app's objects.
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()
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With