In Realm, if I have a linkingOjbects relationship set-up, how should I handle deletions and not be left with orphans especially when it's a many-to-many inverse relationship?
Using the Realm Person and Dog examples, assuming that a Person in this instance is a Dog walker and that a Dog can be walked by a different Person on different days
So a Dog object is assigned to multiple Person objects. Each Person object can see the Dog. Assuming the following object structure and data
Person : Object {
dynamic var name:String = ""
let dogs = List<Dog>()
}
Dog : Object {
dynamic var name: String = ""
let walkers = LinkingObjects<fromType: Person.self, property:"dogs">
}
Person A
dogs = [Fido,Rover]
Person B
dogs = [Fido, Rover]
Person A no longer needs to walk Fido, so would the correct approach be
personA.dogs.remove(objectAtIndex:idxOfFido)
This would update the reference in personA but would it also update the reference in dog?
Secondly if personB also no longer needs to walk Fido I would do
personB.dogs.remove(objectAtIndex:idxOfFido)
but would this then leave an orphaned reference to Fido in my Dog realm as it is no longer walked by anyone? Would it then be up to me to do a check such as
if fido.walkers.count == 0 {
//remove Fido
}
1.) linking objects are the "other side of the relationship", so if you update it on one side, then the other side also updates. Removing fido from persons.dog will remove person from dog.walkers.
2.) just because a dog isn't walked by someone doesn't mean it's dead, so yes, you'd need to delete orphaned dog manually.
Related
I know that you can observe changes to entities you've fetched with #ObservedObject.
The problem is best illustrated with an example: If I had an entity school with a one to many relationship to students and each student has a property numberOfPencils then I want to recalculate the sum of all pencils within a school whenever a student in the school changes his numberOfPencils property.
If I access all the students through the schools entity (which is an #ObservedObject) then I only get updates to the sum if a new student is added but not when an existing student changes his numberOfPencils because student objects can only be observed one by one as far as I know.
The only thing I could come up with is setting up a NSFetchedResultsControllerDelegate with a fetchRequestPredicate that gets all the students associated with a school through some keyPath from student to school which uniquely identifies the school. But that seems like a hacky way to do it and there surely must be a better solution that is eluding me.
Basically I want to get notified of changes to any of the students associated with a school.
Derived properties is definely a good way to go.
But to clarify: #ObservedObject can also get updated on change of the item. You just have to manually trigger the update with objectWillChange.send()
Say your School cell looks like this:
struct SchoolCell: View {
#ObservedObject var school: School
var body: some View {
HStack {
let pencils = school.pupilsArray.reduce(0, {$0 + $1.numberOfPencils})
Text(school.name)
Spacer()
Text(verbatim: "\(pencils) pencils")
.foregroundColor(.secondary)
}
}
}
then the Pupil modify would work like this:
// ...
#ObservedObject var school: School
// ...
Button("+10 Pencils") {
school.objectWillChange.send()
pupil.numberOfPencils += 10
try? viewContext.save()
}
It is important though that SchoolCell IS an own struct, so it gets refreshed when its #ObservedObject school changes.
I'm trying to create many-to-many relations with mongoengine as discussed in here
using a ListField of ReferenceField(s).
When removing an existing element from the database that's being referenced, the reference should automagically be removed from any referring lists. But this doesn't seem to happen:
import mongoengine
print(mongoengine.__version__)
from mongoengine import *
0.24.2
conn=connect('tumblelog')
class Food(Document):
name = StringField(default="nakki")
ingredient = StringField(default="jauhot")
class ExtendedUser(Document):
email = StringField(default="email")
first_name = StringField(max_length=50, default="1")
last_name = StringField(max_length=50, default="2")
address = StringField(max_length=50, default="3")
# http://docs.mongoengine.org/guide/defining-documents.html#many-to-many-with-listfields
foods = ListField(ReferenceField(Food, reverse_delete_rule=PULL))
# Food.register_delete_rule(ExtendedUser, "foods", PULL) # doesn't help..
for obj in Food.objects:
obj.delete()
for obj in ExtendedUser.objects:
obj.delete()
for i in range(0,1):
ExtendedUser(first_name="extended_user"+str(i), last_name="surname"+str(i), address="lokkisaarentie "+str(i), email=str(i)).save()
for user in ExtendedUser.objects:
print(user.first_name, user.id)
extended_user0 635998a040595671f29aede0
user.foods
[]
for i in range(0,3):
Food(name="bacalao"+str(i), ingredient="kala"+str(i)).save()
foodlis=[]
for obj in Food.objects:
print(obj, obj.name)
foodlis.append(obj)
Food object bacalao0
Food object bacalao1
Food object bacalao2
user.foods=[foodlis[0], foodlis[1]]
user.foods
[<Food: Food object>, <Food: Food object>]
user.save()
<ExtendedUser: ExtendedUser object>
for obj in Food.objects:
obj.delete()
for obj in Food.objects:
print(obj, obj.name)
# food objects got deleted, so what are these guys still doing in here!?
user.foods
[<Food: Food object>, <Food: Food object>]
So I would have expected the Foods to be gone from the list. But they are not. A bug in mongoengine? Or have I missunderstood something?
In Realm, I had problem understanding ( Im new in Realm T,T ) the implementations of LinkingObjects , let's say a Person could have more than one Dog ( List of Dog ) so I would write the code such as below:
Person : Object {
dynamic var name:String = ""
let dogs = List<Dog>()
}
Dog : Object {
dynamic var name: String = ""
let walkers = LinkingObjects<fromType: Person.self, property:"dogs">
}
lets say
Person A
dogs = [Alex,Cindy]
Person B
dogs = [Doggo,Kiba]
if I delete Alex using Realm.delete then it become Person A dogs = [Cindy]
this is quite straight forward.
but when I remove LinkingObjects from the class such as below:
Person : Object {
dynamic var name:String = ""
let dogs = List<Dog>()
}
Dog : Object {
dynamic var name: String = ""
}
the app still can run normally without any diff, I can still add Person 3 and append a list of Dog. Also when deleting ( Realm.delete ) there is no difference as when I'm adding LinkingObject to the Dog class. what's the difference here? When can I use LinkinObject? Can anyone please explain? or write some more understandable code?
Already read all the previous answer but still I need another explanation. Thank You So Much!!
You can think of LinkingObjects almost as a computed property - it automagically creates an inverse link to the parent object when the child object is added to the parent objects List.
So when a Dog is added to a person's dogs list, a person reference is added to the Dog's walkers list. Keeping in mind that it's a many to many relationship so technically if Person A adds Doggo, and Person B adds Doggo, the Doggo's inverse relationship 'walkers' will contain Person A and Person B
the app still can run normally without any diff
Which is true, it doesn't affect he operation of the app. HOWEVER the difference is that by removing the walkers LinkingObjects, there's no way to query Dogs for their Person and get Dog Results (i.e. you can't traverse the graph of the relationship back to the person)
In other words we can query Person for kinds of dog stuff
let results = realm.objects(Person.self).filter("ANY dogs.color == 'brown'")
which returns a results object contains Persons that have a brown dog. However, we don't know which dog they have is brown; could be one, could be three.
However, suppose you want to get a results object containing specific brown dogs and want the owners name for each - you can't really do that without an inverse relationship (LinkingObjects)
let dogResult = realm.object(Dog.self).filter("color == 'brown'")
for dog in dogResult {
let person = dog.walkers.first!.name //assume there is only one owner
print(person.name)
}
The dogResults will only contain brown dogs, and you know specifically which ones they are.
So that's a big difference; without LinkingObjects we rely on returning Person objects and then iterating or processing each to get to the data we want. With LinkingObjects we can specifically get the objects we want to work with.
It's super handy when you want to add an observer to results to watch for changes - say on Brown dogs - to be notified of changes.
I am rather new to the Entity Framework, so I am probably overlooking something simple here.
In my controller class I am adding a new Category entity to the database, next I am using that entity as a property on a Course entity. When I save the Course entity, the Category is saved to the database AGAIN, while I was hoping the new Course would reference the Category that was already inserted.
The (simplified) controller code that saves the first Category:
// Create and save the category
Category category = new Category {Name = "Test category"};
category = context.Categories.Add(category);
context.SaveChanges(); // The category object now has a CategoryId (the pk of the record)
// Create and save the course
Course course = new Course {
FullDescription = "This is a new course",
Name = "My new course",
Category = category // Hoping this will have EF make a link to the just inserted category
};
context.Courses.Add(course);
context.SaveChanges(); // Saves the Course AND creates a **new** Category in the db
The problem seems to be that I call saveChanges() twice. What works is removing the first call to context.saveChanges(), BUT, this is not my actual code. In my application I use a repository pattern and adding a category is done by calling categoryRepository.AddCategory(Category category). And saving the Course is done in exactly the same way, by calling courseRepo.AddCourse(Course course) that also contains a call to saveChanges().
public Category AddCategory(Category category)
{
category = context.Categories.Add(category);
context.SaveChanges();
return category;
}
I don't want to remove the calls to saveChanges() in AddCourse() and AddCategory(), because I want these to be atomic operations.
I was hoping that returning the category and subsequently using the category as a property on a new Course would link that course to the category, but apparantly that is not the case. How do I link my Course to a category that is already present in the database?
I'm not sure how your data model is structured but you could do something like this.
course.CategoryId = category.CategoryId;
That way you map the actual foreign key in the relationship and it does the same thing.
Category category = new Category {Name = "Test category"};
Course course = new Course {
FullDescription = "This is a new course",
Name = "My new course",
Category = category
};
courseRepo.AddCourse(course);
You can use only AddCourse for adding both entities, if yours repositories has the same context. If each repository has their own context, you should attach category to the courseRepo context or load entity into it (but I suppose it not suitable for you because you have different repositories).
I the School class I have this code:
from student in this.Students where student.Teacher.Id == id select student
The Student class there are two relationships: Teacher and School. In the School class I'm trying to find out all the students whose Teacher has a given id.
The problem is that I get
System.NullReferenceException: Object reference not set to an instance of an object.
in the statement
student.Teacher.Id
I thought of doing this.Students.Include("Teacher"), but this.Students doesn't have such a method. Any ideas how can I perform that query?
You show this line of code:
from student in this.Students where student.Teacher.Id = id select student
First, the = should be a ==
Is that just a typo?
Second, you don't need Include for the following, corrected query, if you don't dereference Teacher later on:
var q = from student in SomeObjectContext.Students
where student.Teacher.Id == id
select student;
LINQ to Entities doesn't require Inlcude for just the where clause.
You would need Include if you later iterated the results and dereferenced Teacher:
foreach (var student in q)
{
Console.WriteLn(student.Teacher.Id);
}
Third, you show this error:
System.NullReferenceException: Object reference not set to an instance of an object.
This is not a LINQ to Entities error. Is the error actually on that line of code, or is it somewhere else?
Also, what's this? If it's not an ObjectContext, then you are in likely LINQ to Objects, not LINQ to Entities. In that case, you wouldn't have Include available. If this is the case, then where does this.Sbudents come from?
I've found the debugger let me walk throu each iteration of the query:
from student in this.Students where student.Teacher.Id == id select student
so I've got to see student.Teacher.Id == id many times. Every time I was debugging it didn't happen and everything worked just fine. I turned the expression into:
from student in this.Students where student.Teacher != null && student.Teacher.Id == id select student
and it not only stopped crashing, but it worked fine as well (all students where selected as expected). I'm not totally sure why.
The Include clause can be used in linq to entities query. For example:
using (YourDataContext db = new YourDataContext())
{
var query = from s in db.Students.Include("Teacher")
where s.Teacher.ID == 1
select s;
//etc...
}
I guess that this.Students is collection pulled into memory already, so you might consider this code in the part you are retrieving students from dbs. If you want to load Teacher of the Student later on ( very important is that student entities are tracked by ObjectContext!) you can use the Load method on TeacherReference property, but for every student in this.Students collection separately:
student.TeacherReference.Load();
Hope this helps