CoreData predicate - keypath not found - core-data

I have a Core Data object called User and another one called Conversation. I added a relationship between the two. The relationship is called lastConversation.
This is how it looks like:
I also added it to the User.swift class as so:
#NSManaged public var lastConversation: Conversation?
Now i'm trying to use this property in order to predicate out from CoreData. Here is a sample of a predicate that doesn't work for me:
NSPredicate(format: "lastConversation.uid == 123")
But when I run it, it crashes and says:
SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x600000a5d5e0> , keypath lastConversation.uid not found in entity LPUserEntity with userInfo of (null)
Can anyone help me?

Related

Should I create a "Student" model class if I have a CoreData Entity called "StudentEntity"?

I am new to CoreData.
I'm building an ios 10 (Swift3) app with offline support. I need to save "Student" details in the app in CoreData.
I have managed to save in CoreData and Fetch the results by following this lesson.
CoreData IOS10 Swift
My question is how to maintain an entity model class like this?
class Student: NSObject {
//Attributes
var id:String = ""
var contractNo:String = ""
var name:String = ""
}
Sometimes, I need to get a student list from an API and save in "Students" dictionary then I might show a list of students in a "Tableview".
In Websites, we would have a table called "Users" and a corresponding model class call "User.php" which will handle all create/edit/delete/update actions.
I'm not very sure about the best way in IOS10 with Swift3. (It seems we don't need to create Student-CoreData NSManagedObject subclass in IOS10)
So I just named my CoreData entity "StudentsEntity"(to avoid ambiguous name error) and created a class called "Student.swift" inside the Models group.
Now I can do this
var studentArray:[Student] = [Student]()
Can any of those CoreData Gurus out there advise me on this?
Can I continue to do this or is there a better way to achieve this workflow?
Any comments or suggestions would be appreciated.
Thanks a lot!

Non-optional to-many relationship allows nil or empty set

I have a sample data structure:
Table "Groups" and table "Items". Groups can contains zero or more items and item must be linked to minimum one or more groups:
Groups |--------- to-many, optional ---------->>|Items
---------|<<------- to-many, non-optional! -------|--------
groupName| |itemName
items | |groups
I create NSManagedObject subclasses. Both are trivial but here is Items:
class Items:NSManagedObject {
#NSManaged var itemName:String
#NSManaged var groups:Set<Groups> //attention: there is no ! or ?
}
In code below I expecting to catch error:
var item = Items()
item.itemName = "AAAA"
do {
try Items.moc.save() //moc - static field in Items "linked" to ManagedObjectContext
}
catch {
print(error)
}
But there is no errors! item saves to CoreData with empty groups. I can't figure out why? But if I make Items to Groups relation non-optional to-one (in class Items groups field became #NSManaged var group:Groups) exception throws as I expected.
I know, that I can implement in Items class function validateGroups, where I can check if groups nil or empty, but I want to know: is there my mistake that I cant find or it is Core Data bug (or feature)? More, I like to implement storage logic into database (in sql I very like triggers, I can't live without foreign keys, constraints etc).
So, please, help me to understand this. Thanks!
Problem solved, thanks to Willeke!
When I set minimum count in Item's entity relation properties problem disappeared.

How to change managed object class name before fetching

I have a Swift app that uses CoreData. I created List entity with class MyAppTarget.List. Everything is configured properly in .xcdatamodeld file. In order to fetch my entities from persistent store, I am using NSFetchedResultsController:
let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName("List", inManagedObjectContext: managedObjectContext)
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "name", ascending: true) ]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: "ListFetchedResultsControllerCache")
and it works like expected, returning array of MyAppTarget.List objects when fetching.
However, I would like to use it inside another target, for unit testing. I added List class to MyUnitTestTarget, so I can access it inside the unit test target. The problem is that the fetched results controller returns MyAppTarget.List objects, not the MyUnitTestTarget.List objects. In order to make the List entity testable, I have to make it public along with all methods that I need to use and I would like to avoid this.
I tried to change the managedObjectClassName property on NSEntityDescription:
fetchRequest.entity.managedObjectClassName = "MyUnitTestTarget.List"
but it generates exception:
failed: caught "NSInternalInconsistencyException", "Can't modify an immutable model."
The documentation states that
[...] once a description is used (when the managed object model to which it belongs is associated with a persistent store coordinator), it must not (indeed cannot) be changed. [...] If you need to modify a model that is in use, create a copy, modify the copy, and then discard the objects with the old model.
Unfortunately, I don't know how to implement this flow. I wonder if there is a way to change the managed object class name in runtime, before fetching the entities with NSFetchedResultsController?
It occurs that solution to my problem was pretty simple. In order to make it working, I had to create a copy of managedObjectModel, edit its entities and create NSPersistentStoreCoordinator with the new model. Changing the managedObjectClassName property on NSEntityDescription instance is possible only before model to which it belongs is associated with NSPersistentStoreCoordinator.
let testManagedObjectModel = managedObjectModel.copy() as NSManagedObjectModel
for entity in testManagedObjectModel.entities as [NSEntityDescription] {
if entity.name == "List" {
entity.managedObjectClassName = "CheckListsTests.List"
}
}
This also solves my other problem with unit testing CoreData model entities in Swift.
You can dynamically alter the class name of the NSManagedObject subclass with something like:
let managedObjectModel = NSManagedObjectModel.mergedModelFromBundles([NSBundle.mainBundle()])!
// Check if it is within the test environment
let environment = NSProcessInfo.processInfo().environment as! [String : AnyObject]
let isTestEnvironment = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"
// Create the module name based on product name
let productName:String = NSBundle.mainBundle().infoDictionary?["CFBundleName"] as! String
let moduleName = (isTestEnvironment) ? productName + "Tests" : productName
let newManagedObjectModel:NSManagedObjectModel = managedObjectModel.copy() as! NSManagedObjectModel
for entity in newManagedObjectModel.entities as! [NSEntityDescription] {
entity.managedObjectClassName = "\(moduleName).\(entity.name!)"
}

Swift CoreData subclass not working

i know that this has been asked a couple of times but the usual solution does not seem to work for me. I created a CoreData entity and a subclass for it using <ProjectName>.<SubclassName> syntax as class name. Creating a new object like this:
let object = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: CoreDataManager.sharedInstance.managedObjectContext) as User
println("-->\(object)<--")
object.setValue(12, forKey: "userID")
object.setValue("username", forKey: "username")
the console output:
although the object does not get printed in the console it seems to have been in some way created and setting a value on that object refers to a Core Data Object
when i use it without a subclass it works as expected (setting Class name back to default):
let object = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: CoreDataManager.sharedInstance.managedObjectContext) as NSManagedObject
println("-->\(object)<--")
output:
Here's my subclass declaration:
import Foundation
import CoreData
class User: NSManagedObject {
#NSManaged var userID: NSNumber
#NSManaged var username: String
}
and here's the core data model form:
What's wrong with the code? Or do i miss anything?
For printing out the value of an NSManagedObject, do not use:
println(...)
But rather use:
NSLog(...)
Please find an example of using NSLog and an NSManagedObject below:
/**
* Called when the user clicks on the save button.
*/
#IBAction func saveTapped(sender: AnyObject) {
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference MOC
let context: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("List", inManagedObjectContext: context)
// Create instance of put data model and initialize
var newItem: List = List(entity: en!, insertIntoManagedObjectContext: context)
// Map our properties
newItem.item = textFieldItemName.text
newItem.quantity = textFieldQt.text
newItem.info = textFieldMoreInfo.text
// Save our context
var error: NSError? = nil;
if (context.hasChanges) {
if (!context.save(&error)) { // save failed
println("Save Failed: \(error!.localizedDescription)")
} else {
println("Save Succeeded")
}
}
NSLog("newItem: %#", newItem)
// Navigate back to root ViewController
self.navigationController?.popToRootViewControllerAnimated(true)
}
Note: I do not know the exact reason (bug, or implementation maybe ...) but it turns out that it does not print out a value when we use println(...) function, instead of that it returns an empty String.
Consequently I recommend to all of you guys to use NSLog(...) function instead of println(...) when you want to print out the value of an NSManagedObject.
If you print the expression CoreDataManager.sharedInstance.managedObjectContext twice, do you get a different pointer each time?
It sounds like the managed object context is getting deallocated right after you use it, which indicates that your CoreDataManager.sharedInstance.managedObjectContext property is returning a new managed object context every time, not the same one, or your sharedInstance property is returning a new instance every time.
A managed object's in-memory state is stored as a weak reference to a managed object context. When the context drops out from underneath you (e.g. it's no longer referenced and so is deallocated), your managed object's storage disappears.
A few good indicators that this is happening:
You haven't saved your managed object context yet but a newly created managed object prints as fault
You get weird errors when you try to set a property
You get weird errors when you try to retrieve a property you just set
Include the following at the top of your .swift file
import CoreData
The reason for not working is it doesn't know which library to reference.
Hope it helps
try creating it with this method :
let entityDescripition = NSEntityDescription.entityForName(“User”, inManagedObjectContext: managedObjectContext)
let user = User(entity: entityDescripition, insertIntoManagedObjectContext: managedObjectContext)

How do you get a new Core Data Entity attribute to be reflected in the NSManagedObject for that Entity?

I'm trying to implement a getter on one of my db classes. But when I execute the following line of code, where "obj" is an NSManagedObject:
return [obj valueForKey:#"activationData"];
I get the following NSUnknownKeyException:
'[ valueForUndefinedKey:]: the entity Blueprint is not key value coding-compliant for the key "activationData".'
I just recently added a String attribute named "activationData" to my "Blueprint" entity using Xcode. But when I run the app the NSManagedObject that represents Blueprint entities does not include the new "activationData" attribute, which apparently is the cause of the crash.
The NSManagedObject looks like this, but I expected it to show the new Attribute along with the createDate, name and order attributes:
<NSManagedObject: 0x5138c90> (entity: Blueprint; id: 0x513a2e0 <x-coredata://8C586BB8-B9E7-4FD7-84CB-5CE66FB221E6/Blueprint/p2> ; data: {
createDate = "2012-02-21 15:49:00 +0000";
name = "Feb 17 test";
order = 2;
})
Fyi, user1226119's answer (below) reminded me that I used the Organizer to extract the sqlite db from my device and inspect it with SQLite Manager to verify things. Sure enough there is still no new activationData field in the Blueprint table. The table looks the same as it always did.
I think I must have missed some necessary step for adding a new Attribute to an existing db Entity.
Your model has not change in your app. You must delete your old application and re-deploy your app on your device.
The solution was to update the pathForResource method call in the code that returns the NSManagedObjectModel. I had indeed created a new xcdatamodel version of the db before adding the attribute, but apparently you are supposed to refer to it using the following code, which retrieves the model your app will use.
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"MyDB Version5" ofType:#"mom" inDirectory:#"ASSIST.momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel;
}
I had to put the new database version's name ("MyDB Version5") as the pathForResource parameter. Previously it was "MyDB Version5".

Resources