I'm trying to update a relationship in Core Data but am experiencing this issue. I have 2 entities: Trip and GPSLocation. A Trip can have many GPSLocation but a GPSLocation can only be in one Trip, hence an one-to-many relationship from Trip to GPSLocation. I set up my entities in Xcode's model editor like so:
Entity: GPSLocation relationship: trip destination: Trip inverse: gps Type: To one
Entity: Trip relationship: gps destination: GPSLocations inverse: trip Type: To many
Assuming the variable trip is an instance of my Trip entity. In my update routine, I have:
let request = NSBatchUpdateRequest(entityName: "GPSLocations")
request.predicate = somePredicate
request.propertiesToUpdate = ["trip": trip]
request.resultType = .UpdatedObjectIDsResultType
do {
let responses = try managedContext.executeRequest(request) as! NSBatchUpdateResult
print responses.result!
} catch let error as NSError {
print(error)
}
This code is giving me an NSInvalidArgumentException Reason: 'Invalid relationship' and I'm not sure why. Any help would be very appreciated! Thank you.
EDIT: My predicate is a pretty simple one: NSPredicate(format: "SELF = %#", "1"). I confirmed that record with pk 1 exists via a database visualizer.
EDIT 2:
So I did some further testing and noticed something interesting about the 2 routines that I wrote for creating and updating records in entities. Unlike with the update routine, I don't get this invalid relationship problem when I use the create routine. Here is my code for both routines:
// MARK - this routine works fine
func createRecord(entityName: String, fromKeyValue: [String:AnyObject]) -> AnyObject {
let entityDesc = NSEntityDescription.entityForName(entityName, inManagedObjectContext: managedContext)
do {
let entityType = NSClassFromString("myapp." + entityName) as! NSManagedObject.Type
let entity = entityType.init(entity: entityDesc!, insertIntoManagedObjectContext: managedContext)
for key in fromKeyValue.keys {
entity.setValue(fromKeyValue[key], forKey: key)
}
try managedContext.save()
return entity
} catch let error as NSError {
return error
}
}
// MARK - this one gives me problem..
func updateRecords(entityName: String, predicate: NSPredicate, keyValue: [String:AnyObject]) -> AnyObject {
let request = NSBatchUpdateRequest(entityName: entityName)
request.predicate = predicate
request.propertiesToUpdate = keyValue
request.resultType = .UpdatedObjectIDsResultType
do {
let responses = try managedContext.executeRequest(request) as! NSBatchUpdateResult
return responses.result!
} catch let error as NSError {
return error
}
}
Related
let's imagine that we have 2 entities:
-People (name, age, ..)
-House (color)
we recorded the data several times with house.addToPeople (newPeople) for each house
we want to get all the people of the house colored blue
how do we fetch this?
I tried this code but it gets all the people
let appD = UIApplication.shared.delegate as! AppDelegate
let context = appD.persistentContainer.viewContext
let peopleFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "People")
let houseFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "House")
houseFetch.fetchLimit = 1
houseFetch.predicate = NSPredicate(format: "color = %#", "blue")
...
let res = try? context.fetch(peopleFetch)
let resultData = res as! [People]
how to do this ?
Try this function. What it does is fetching all of the People and creating an array with all of the results.
func getAllItems() -> [People]? {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "People")
request.returnsObjectsAsFaults = false
do {
let result: NSArray = try context.fetch(request) as NSArray
return (result as? [People])!
} catch let error {
print("Errore recupero informazioni dal context \n \(error)")
}
return nil
}
If you want to perform your search following certain criteria such as a color, use the following code after request:
//Here i'm searching by index, if you need guidance for your case don't hesitate asking
request.predicate = NSPredicate(format: "index = %d", currentItem.index)
Edit: actually the code above is just to get all of the people, if you want to base your search on the houses do the following:
func retrieve()-> [People]{
//Fetch all of the houses with a certain name
let houseRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "House")
houseRequest.predicate = NSPredicate(format: "name = %#", "newName") //This seearch is by name
houseRequest.returnsObjectsAsFaults = false
do {
//put the fetched items in an array
let result: NSArray = try context.fetch(houseRequest) as NSArray
let houses = result as? [Houses]
//Get the people from the previous array
let people: [People]? = (houses.people!.allObjects as! [People])
return people
} catch let error {
print("Errore recupero informazioni dal context \n \((error))")
}
return nil
}
Thank you for your answer !
In this example "houses" is an array, so we have to add an index ➔ houses[0].people!.AllObjects
And thank you very much for your explanation.
i am using following Code to eliminate attributes while fetching from my CoreDataModel called industry. Still i am able to access those attributes which have not requested using fetchRequest.propertiesToFetch
let fetchRequest = NSFetchRequest(entityName: "Industry")
fetchRequest.propertiesToFetch = ["id","industry_name"]
After this i am using following code:
industryObject.industry_name=tuple.value(forKey: ""ind_subtype"") as? String
"ind_subtype" i have not specified in *.propertiesToFetch* and i am still able to access it
func fetchIndustryNamesWithId()->[IndustryData]{
var industryData=[IndustryData]()
//fetchRequest.predicate = NSPredicate(format: "firstName == %#", firstName)
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Industry")
fetchRequest.propertiesToFetch = ["id","industry_name"]
do {
let tuples = try managedObjectContext.fetch(fetchRequest)
for tuple in tuples{
let industryObject=IndustryData()
industryObject.id=tuple.value(forKey: "id") as? Int
industryObject.industry_name=tuple.value(forKey: "industry_name") as? String
industryObject.ind_subtype=tuple.value(forKey: "ind_subtype") as? String
industryData.append(industryObject)
}
return industryData
} catch {
let fetchError = error as NSError
print(fetchError)
}
return industryData
}
This is how it works. When you set propertiesToFetch CoreData will returns ManagedObject as a partially faulted. It means that some of the properties are populated some are not. When you access property that is not populated, core data will fulfil it for you but it probably will have performance impact because in most cases will require roundtrip to row cache.
For more info google for CoreData Faulting
I'm discovering new concepts as a fresh developer, I have been trying to understand core data, and run into an issue with a tutorial I've been walking through. I am getting an error when I call an item using the Object ID. The error is - Type 'Person.Type' has no subscript members. it may be because I am just not doing it correctly, or some other reason. I'm sure someone can shine some light on the subject
Here is a function I wrote to get a specific item out of the Data Stack,
func getObjectById(id: NSManagedObjectID) -> Person?{
return context.objectWithID(id) as? Person
}
Here is how I am calling the function
func callFirstObject(){
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let personService = PersonService(context: context)
let firstPerson = personService.getObjectById(Person[0].objectID!)
}
and from there I am just calling callFirstObject() inside a button.
Edit: I have a function to call all of my objects
func getAllObjects() -> [Person]{
return getObject(withPredicate: NSPredicate(value: true))
}
and a function to call all of my objects with a predicate
func getObject(withPredicate queryPredicate: NSPredicate) -> [Person]{
let fetchRequest = NSFetchRequest(entityName: Person.entityName)
fetchRequest.predicate = queryPredicate
do {
let response = try context.executeFetchRequest(fetchRequest)
print("\(response)")
return response as! [Person]
} catch let error as NSError {
// In case of failure
print("There was an error - \(error)")
return [Person]()
}
}
I am just trying to call a specific name in the stack.
If more information is needed, I am glad to provide.
Person has no subscript members because Person is the class name. The subscript, [0], should be called on an array of Person objects.
That could look something like this:
func callFirstObject(){
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let personService = PersonService(context: context)
// Person Array
let persons: [Person] = [person1, person2, person3 ]
let firstPerson = personService.getObjectById(persons[0].objectID!)
}
Edit: I'm kind of confused the logic though. If you have the person already, you have to in order to access the objectID, then I don't see why you need to fetch it again. Can we see some more context around these two methods?
Answering your question from the comment below:
If you want to get all of the records for the Person model you can do as follows:
let appDelegate =
UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Person")
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
people = results as! [NSManagedObject]
} catch let error as NSError {
print("Could not fetch \(error)")
}
This will give you all of the Person objects stored in Core Data
My project has Category and Recipe entity which has one to many relationship.
When a user creates a new recipe, selecting category is optional.
If not selected, a default category will be used to establish relationship with a recipe.
When createRecipe() is fired, it will add correct category relationship to Recipe entity. But the problem is, there will be an empty record added in Category entity.
I made sure both changedCategory and defaultCategory are not "" so not sure why I'm seeing this "" added in Category entity.
I'm checking the data entry with sqlitebrowser.
How do I prevent en empty record being added to Category entity with the following code?
func createRecipe(sender: AnyObject!) {
if let title = recipeTitle.text where title != "" {
let category = obtainCategoryEntity(changedCategory, defaultCategory: defaultCategory, context: self.context)
let recipe = (NSEntityDescription.insertNewObjectForEntityForName("Recipe", inManagedObjectContext: self.context) as! Recipe)
recipe.category = category
recipe.title = title
...
do {
try self.context.save()
} catch {
print("Could not save recipe")
}
}
}
func obtainCategoryEntity(changedCategory: String, defaultCategory: String, context: NSManagedObjectContext) -> RecipeCategory {
var category = NSEntityDescription.insertNewObjectForEntityForName("RecipeCategory", inManagedObjectContext: self.context) as! RecipeCategory
let fetchRequest = NSFetchRequest(entityName: "RecipeCategory")
if changedCategory != "" {
fetchRequest.predicate = NSPredicate(format: "name == %#", changedCategory)
} else {
fetchRequest.predicate = NSPredicate(format: "name == %#", defaultCategory)
}
if let results = (try? context.executeFetchRequest(fetchRequest)) as? [RecipeCategory] {
if results.count > 0 {
category = results[0]
}
}
return category
}
In obtainCategoryEntity, you need to set category.name to either changedCategory or defaultCategory if the fetch request does not find an existing category with that name.
I am trying to implement custom class to handle core data operations. It works great when creating new values. However when I want to update values I get nil entries in core data. Here is my code so far
/**
Update all records in given entity that matches input records
- parameters:
- entityName: name of entity to fetch
- updateBasedOnKey: name of key which will be used to identify entries that are going to be udpated
- values: NSMutableArray of all elements that are going to be updated
- important: if object with given updateBasedOnKey doesnt exist it will be created
- returns: nothing
*/
func updateRecord(entity: String, updateBasedOnKey: String, values: NSMutableArray){
let entityDescription = NSEntityDescription.entityForName(
entity, inManagedObjectContext: self.managedObjectContext)
let results = getRecords(entity)
for(elements) in values{
var newEntry = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
//Determine whether to add new result or update existing
if(results.count > 0){
for result in results{
let entry = result as! NSManagedObject
if let keyValueToCompare = entry.valueForKey(updateBasedOnKey){
if (keyValueToCompare.isEqual(elements.valueForKey(updateBasedOnKey)) ){
//asign newEntry to result if found in entries
newEntry = entry
}
}
}
}
//update entry with new values
for(key, value) in elements as! NSMutableDictionary{
newEntry.setValue(value, forKey: key as! String)
}
//Try to save resulting entry
do {
try newEntry.managedObjectContext?.save()
} catch {
print(error)
}
}
}
/**
Fetch all records of given Entity in Core Data Model
- parameters:
- entityName: name of entity to fetch
- returns: NSArray of all records in given entity
*/
func getRecords(entity:String) -> NSArray{
let entityDescription = NSEntityDescription.entityForName(entity, inManagedObjectContext: self.managedObjectContext)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entityDescription
var result = NSArray()
do {
result = try self.managedObjectContext.executeFetchRequest(fetchRequest)
} catch {
let fetchError = error as NSError
print(fetchError)
}
return result
}
I think that problem is somewhere in asigning newEntry a NSManagedObject.
Any ideas how to fix this and get rid of nils?
Thanks in advance
EDIT:
this is actual working code created by implementing Wain suggestion
func updateRecord(entity: String, updateBasedOnKey: String, values: NSMutableArray){
let entityDescription = NSEntityDescription.entityForName(
entity, inManagedObjectContext: self.managedObjectContext)
let results = getRecords(entity)
for(elements) in values{
//set to true if value was already found and updated
var newEntry : NSManagedObject?
//Determine whether to add new result or update existing
if(results.count > 0){
for result in results{
let entry = result as! NSManagedObject
if let keyValueToCompare = entry.valueForKey(updateBasedOnKey){
if (keyValueToCompare.isEqual(elements.valueForKey(updateBasedOnKey)) ){
//asign newEntry to result if found in entries
newEntry = entry
}
}
}
}
if newEntry == nil {
newEntry = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.managedObjectContext)
}
for(key, value) in elements as! NSMutableDictionary{
newEntry!.setValue(value, forKey: key as! String)
}
}
}
You're right, the problem is that you're creating and inserting a new object each time. Instead you should be passing the object to update or running a fetch request to find it, then updating it.
It looks like your intention is to fetch, and the new entry should just be a reference, not initialised. So:
var newEntry : NSManagedObject?