Fetch Coredata with One to Many Relationship in Swift

fetch coredata with one to many relationship in swift

I solved the problem by using the NSPredicate to connect the entity2 data with that attribute object from entity1 by using this line of code

let myFetch:NSFetchRequest<entity2> = entity2.fetchRequest()
let myPredicate = NSPredicate(format: "toEntity1-relationship == %@", (myTransferdObject?.name!)!)
myFetch.predicate = myPredicate
do {
usersList = try myContext.fetch(myFetch)
}catch{
print(error)
}

swift core data how should I save and fetch the one to many relationship and assign value to my viewModel entity

Rather creating relation like this you can do it saving the class id in teacher and student and get them on the basis of id like as:

class ClassEntity {
let id:String
var classTitleName: String
var classRank: Int


init (id:String,
classTitleName: String,
classRank: Int) {
self.id = id
self.classTitleName = classTitleName
self.classRank = classRank
}

}

then your teacher and student

class StudentEntity {
var classId:String
var studentName: String
var studentGender: String

init(classId:String,studentName: String,
studentGender: String){
self.classId = classId
self.studentName = studentName
self.studentGender = studentGender
}
}

class TeacherEntity {
var classId:String
var teacherName: String
var teacherGender: String

init(classId:String,teacherName: String,
teacherGender: String){
self.classId = classId
self.teacherName = teacherName
self.teacherGender = teacherGender
}
}

then create coredatastack

import Foundation
import CoreData

class CoreDataStack: NSObject {

private let modelName: String
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()

init(modelName: String) {
self.modelName = modelName
}

private lazy var storeContainer: NSPersistentContainer = {

let container = NSPersistentContainer(name: self.modelName)
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
return container
}()


func saveContext () {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}

func updateContext() {
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}

func clearChanges() {
managedContext.rollback()
}

func deleteReminder(reminder:Reminder) {
managedContext.delete(reminder)
updateContext()
}

func deleteNote(note:Note) {
managedContext.delete(note)
updateContext()
}

func deleteFromIds(selectedId:SubCategoryIds) {
managedContext.delete(selectedId)
updateContext()
}

}

then create a manager

import Foundation
import CoreData

class CoreDataManager {

static let shared = CoreDataManager()
private init(){}
lazy var coreDataStack = CoreDataStack(modelName: "YourModelName")

func allClasses() -> [ClassEntity] {
let fetechRequest: NSFetchRequest<ClassEntity> = ClassEntity.fetchRequest()
do {
let results = try coreDataStack.managedContext.fetch(fetechRequest)
return results
} catch {

}
return [ClassEntity]()
}

func allStudentOfClass(classId:String) -> [StudentEntity] {
let fetechRequest: NSFetchRequest< StudentEntity > = StudentEntity.fetchRequest()
fetechRequest.predicate = NSPredicate(format: "classId == %@",
argumentArray: [classId])
do {
let results = try coreDataStack.managedContext.fetch(fetechRequest)
return results
} catch {

}
return [StudentEntity]()
}

func allTeacherOfClass(classId:String) -> [TeacherEntity] {
let fetechRequest: NSFetchRequest<TeacherEntity> = TeacherEntity.fetchRequest()
fetechRequest.predicate = NSPredicate(format: "classId == %@",
argumentArray: [classId])
do {
let results = try coreDataStack.managedContext.fetch(fetechRequest)
return results
} catch {

}
return [TeacherEntity]()
}


}


how you can create a class

 func createClass() {
let id = "Class 1" //or whatever you want to say here
let _class = Class(context: CoreDataManger.shared.coreDataStack.managedContext)
_class.id = id
_class.classTitleName = "Title"
_class.classRank = 12
CoreDataManger.shared.coreDataStack.saveContext()
}

same as how create teacher with this class

 func addTeacher(classId:String) {
let _class = TeacherEntity(context: CoreDataManger.shared.coreDataStack.managedContext)
_class.classId = classId
_class.teacherGender = "Male"
_class.teacherName = "Alex"
CoreDataManger.shared.coreDataStack.saveContext()
}

One to Many Relationship Core Data fetch Data

toHobbies property is NSSet of Any so you need to typecast it or each x in your loop.

For predicates you can use key-paths like “toHobbies.location = %@“

Core Data fetch request one-to-many relationships with NSFetchedResultsController

If you want your FRC to filter Artists by Genre, you will need to add a relationship from Artist to Genre (the inverse of the artists relationship on the Genre entity). As a side note, you should almost always define an inverse for every relationship - there are very few situations where you are better off without an inverse.

I'm not sure whether you would regard each Artist as having only one Genre, or possibly having many - set the relationship as to-one or to-many as appropriate, and name it genre or genres accordingly. The predicate that is then required will be different in each case:

For to-one, you should use

NSPredicate(format:"genre == %@", chosenGenre)

and for to-many you would use

NSPredicate(format:"ANY genres == %@", chosenGenre)

(assuming you already have a reference, chosenGenre, to the genre you wish to filter by. If you in fact have only the genre title, you would use:

NSPredicate(format:"genre.title == %@", chosenGenreTitle)

and for to-many you would use

NSPredicate(format:"ANY genres.title == %@", chosenGenreTitle)

Coredata update/insert for one to many relationship

First, you need to fetch the User.

Then, you have to choices:

  1. user.devices.add(device)

  2. or if it's two ways relationship, then you can call device.user = user

How to fetch Specific Data in One to Many Core Data Relationships?

First...

Fix the naming of your relationships. As others have pointed out in comments on your other question, you have named them back to front: in the Person entity, the to-many relationship to Account should be named "accounts" not "person". Likewise in the Account entity, the to-one relationship to Person should be named "person" not "accounts".

Next...

Although you have defined the "accounts" relationship as to-many, the savePersonData code in this question creates only one Account and one Person - but does not then set the relationship between them. (You can see this in your Output: each Account has nil for its "accounts" relationship).

The code in your previous question did set the relationship (by adding the newAccount to the relationship on the newPerson object). In your code above you could use (after fixing the relationship names):

NSMutableSet *setContainer = [newDevice mutableSetValueForKey:@"accounts"];
[setContainer addObject:newAccount];

but with one-many relationships it is easier to set the inverse relationship:

[newAccount setValue:newDevice forKey:@"person"];

Next...

Your checkBalance method correctly fetches any Person objects whose "mobile_no" attribute matches. But your subsequent code then fetches ALL Account objects - even if you had correctly set the relationship.

If you want only those Account objects that are related to a given Person, that is precisely what the "accounts" relationship represents. So you could just use that relationship to access the related Account objects:

if(!([result count] == 0)) {
NSManagedObject *requiredPerson = (NSManagedObject *)result[0];
NSSet *temp = [requiredPerson valueForKey:@"accounts"];
for (NSManagedObject *object in temp) {
NSString *intValue = [object valueForKey:@"balance"];
NSString *alertString = [NSString stringWithFormat:@"%@",intValue];
[self displayAlertView:@"Available Balance" withMessage:alertString];
}
}

Alternatively, fetch the relevant Account objects directly (without fetching the Person object first) by specifying a predicate that traverses the relationship:

NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSFetchRequest *newFetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Account"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"person.mobile_no = %@",self.textField.text];
[newFetchRequest setPredicate:predicate];

NSMutableArray *temp = [[managedObjectContext executeFetchRequest:newFetchRequest error:nil] mutableCopy];

for (NSManagedObject *object in temp)
{
NSString *intValue = [object valueForKey:@"balance"];
NSString *alertString = [NSString stringWithFormat:@"%@",intValue];
[self displayAlertView:@"Available Balance" withMessage:alertString];
}

Fetch one-to-many relationships in coreData and put it into a tableview

Apply the inverse relationship to User in Task and add an appropriate predicate

let username = "Foo"
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "user.name == %@", username)
let dateSorter = NSSortDescriptor(key: "dueDate", ascending: false)
request.sortDescriptors = [dateSorter]

The way to create a new results controller is discouraged. Create the results controller lazily (once) and change predicate and sort descriptors dynamically.

swift core data relationship one to many for own entity

First update your class Managed Object class to use Sets instead of NSSet

     import Foundation
import CoreData
extension Item {

@NSManaged var by: String?
@NSManaged var dead: NSNumber?
@NSManaged var descendants: NSNumber?
@NSManaged var id: NSNumber?
@NSManaged var isdeleted: NSNumber?
@NSManaged var score: NSNumber?
@NSManaged var text: String?
@NSManaged var time: NSDate?
@NSManaged var title: String?
@NSManaged var type: String?
@NSManaged var url: String?
@NSManaged var kids: Set<Item>
@NSManaged var parent: Item?
@NSManaged var parts: NSSet?

}

Secondly create a fetchRequest named getItemByID with expression
id == $ID

Third create a function to get item by id

    func getItemById(ID:String) -> Item?
{
let fetchRequest : NSFetchRequest = self.managedObjectModel.fetchRequestFromTemplateWithName("getItemByID", substitutionVariables: ["ID": ID])!
do {
let fetchedResult = try managedObjectContext!.executeFetchRequest(fetchRequest) as! [Item]
if fetchedResult.count > 0 {
let result = fetchedResult[0]
return result
}
}
catch {
print("Could not fetch \(error)")
}
return nil
}

After that you have many options to set relations

a) you create relation after adding parent & child to coreData

func addChildToParent(childID:String,parentID:String)
{
if let childItem = getItemById(childID)
{
if let parentItem = getItemById(parentID)
{
parentItem.kids.insert(menuItemCatering)
childItem.parent = parentItem
}
}
saveContext()
}

Core data one to many relationship in swift

To address your questions directly:

I am using the data in a tableView. I have seen in some tutorials that the use: var vakken = [NSManagedObject]() and then use it in a tableview.
Is this also possible with the model I have, and should I use it

It is certainly possible, but it would probably be better to use an NSFetchedResultsController (see the Apple Docs). This is specifically designed to make to easy to populate a tableView with data from Core-Data.

I first thought was to just save the data when the app terminates, so I would have the code in the app delegate, and then just retrive it when the app starts. How would I get the array to the appdelegate and would this be smart?

I wouldn't populate/save the array in the App Delegate. Some people follow Apple's template projects, which build the CoreData stack in the App Delegate; others have a separate class that they instantiate to manage the stack. From the look of your code, it currently uses the former. Your view controllers can then get the NSManagedObjectContext from the App Delegate using:

let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = appDelegate.managedObjectContext

(As beyowulf points out, you should not use AppDelegate().managedObjectContext as that creates a new instance, rather than referring to the existing instance). Once they have a context, your view controllers can fetch the data they need, add or update existing records, etc.

But how would I get from the result my array back or if I use NSManagedObject how can I refer to the attributes and in most particular the grades.

result is an array of NSManagedObjects. You can obtain the attribute values using valueForKey, the "read" equivalent of setValue:(_, forKey:):

let firstObject = result[0]
let firstObjectName = firstObject.valueForKey("name")

In the same way, the grades can be obtained with:

let firstObjectGrades = firstObject.valueForKey("grades")

There are better ways, though: see your final question below.

I also sort my array by name which can be A-Z or Z-A, and I sort by average which can be 10-1 or 1-10. I have the code to sort it in my array. But I don't know how to sort this with core data.

It is easiest to sort the data when you fetch it. To do this specify a NSSortDescriptor (see the Apple Docs) for the fetch:

let fetchRequest = NSFetchRequest(entityName: "Subject")
fetchRequest.sortDescriptors = [NSSortDescriptor(key:"name", ascending:true)

I have also seen a tutorial where they made a class for the attributes. I don't know if I need it nor how to use it?

Yes, use it. It will make life so much easier. Rather than write your own class definition, use the Xcode menu option "Create NSManagedObject subclasses" in the data model editor. It will create the class code for you and will also set each Entity to use the corresponding class. (If you wish to stick with your own code, you will need to amend the "class" for each entity in the data model editor).

Once you have the subclasses properly defined, you can then refer to the attributes and relationships using dot notation, rather than needing to use valueForKey and setValueForKey:

So:

let Subject = NSManagedObject(entity: entitySubject!, insertIntoManagedObjectContext: context)

will become:

let subject = Subject(entity: entitySubject!, insertIntoManagedObjectContext: context)

(Note, variables should begin with lowercase - Classes and Entity names begin with uppercase).

Grade.setValue(vakken.last!.cijfer[j], forKey: "cijfer")

becomes:

grade.cijfer = vakken.last!.cijfer[j]

and

let firstObjectGrades = firstObject.valueForKey("grades")

becomes:

let firstObjectGrades = firstObject.grades

Also, the addTagObject and removeTagObject functions will make it easier to manage the to-many relationship. Your code currently has:

Subject.setValue(NSSet(object: Grade), forKey: "grade")

This will replace any existing grades for that Subject with the Grade object: it doesn't add it to the existing. In fact, for one-many relationships it's far easier to manage the inverse (to-one) relationship:

grade.subject = subject

CoreData will handle the inverse (to-many) relationship automatically for you.

How can i delete one of the grades?

First, don't build separate arrays for each of the Grades attributes: just have one array for the Grades:

var vak: Subject! {
didSet {
// Update the view.
grades = vak.grades.allObjects as! [Grade]
self.tableView.reloadData()
}
}

var grades: Array<Grade>!

You can get the attributes easily enough whenever you need them. For example, in your cellForRowAtIndexPath you might have something like this:

let grade = grades[indexPath.row]
let cijfer = grade.cijfer
let weging = grade.weging
// populate cell labels etc using "cijfer" and "waging"

Your commitEditingStyle can then easily find which Grade is being deleted, and handle accordingly:

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let grade = grades[indexPath.row]
// either remove the Grade from your Subject (which leaves
// the Grade "orphaned"):
grade.subject = nil
// or (probably better) completely delete the grade:
managedobjectcontext.deleteObject(grade)
// then remove it from the "grades" array:
grades.removeAtIndex(indexPath.row)
// and finally delete the corresponding table view row
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
// wrap up by saving the changes to the context:
do{
try mangedobjectcontext.save()
}catch{
print("error")
}
}
}

How can i save the vak(subject) to the context when i just change one of the variable?

Whenever you save the context, it saves ALL the changes to any objects registered with it (inserted into it or fetched using it). So, even if the only change you have made is to amend one attribute of one Subject, just call save() on the context.



Related Topics



Leave a reply



Submit