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?
Related
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 use graphene with graphene-mongo. My graphql schema has a type similar to this:
type Report {
id:ID!
name:String!
}
My graphene class for this type is
class Product(MongoengineObjectType):
class Meta:
model = MongoProduct
and the mongoengine class is
class MongoProduct(mng.DynamicDocument):
name = mng.fields.StringField(required=True)
How can I make the field id required? GraphiQL shows an exclamation mark next to name, but not next to id.
class MongoProduct(mng.DynamicDocument):
id = ObjectIdField(primary_key=True, required=True) # Optional: Add default=bson.ObjectId
name = mng.fields.StringField(required=True)
id can also be a IntField or a StringField but I'd recommend to stick to an ObjectId
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.
I am using https://github.com/ichikaway/cakephp-mongodb.git plugin for accessing mongodb datasource.
I have two Models: Teachers and Subject. I want joint find result on Teacher and Subject.
Here are my two models:
Teacher:
<?php
class Teacher extends AppModel {
public $actsAs = array('Containable');
public $hasOne = array('Subject');
public $primaryKey = '_id';
var $mongoSchema = array(
'name'=>array('type'=>'string'),
'age'=>array('type'=>'string'),
'subjectid'=>array('type'=>'string'),
'created'=>array('type'=>'datetime'),
'modified'=>array('type'=>'datetime'),
);
Subject:
<?php
class Subject extends Model {
public $actsAs = array('Containable');
public $belongsTo= array('Teacher');
public $primaryKey = '_id';
var $mongoSchema = array(
'name'=>array('type'=>'String'),
'code'=>array('type'=>'String'),
'created'=>array('type'=>'datetime'),
'modified'=>array('type'=>'datetime')
);
In Teachers Controller to get joint result, I did:
$results = $this->Teacher->find('all',array('contain'=>array('Subject')));
$this->set('results', $results);
But I am not getting any result from Subjects Collections.
Here is what I am getting:
array(5) {
[0]=>
array(1) {
["Teacher"]=>
array(7) {
["_id"]=>
string(24) "52e63d98aca7b9ca2f09d869"
["name"]=>
string(13) "Jon Doe"
["age"]=>
string(2) "51"
["subjectid"]=>
string(24) "52e63c0faca7b9272c09d869"
["modified"]=>
object(MongoDate)#78 (2) {
["sec"]=>
int(1390820760)
["usec"]=>
int(392000)
}
["created"]=>
object(MongoDate)#79 (2) {
["sec"]=>
int(1390820760)
["usec"]=>
int(392000)
}
}
}
I am a cakephp/mongoDB rookie only, Please guide me to get the desired result.
Edit: I read that mongoDb don't support Join Operation. Then How to manually do it? I mean I can write two find query on each Model, then how to combine both array and set it to view?
As you said, MongoDB does not support joins. Documents in query results come directly from the collection being queried.
In this case, I imagine you would query for Teacher documents and then iterate over all documents and query for the Subject documents by the subjectid field, storing the resulting subject document in a new property on the Teacher. I'm not familiar with this CakePHP module, but you may or may not need to wrap the subjectid string value in a MongoId object before querying. This depends on whether or not your document _id fields in MongoDB are ObjectIds or plain strings.
If Subjects only belong to a single teacher and you find that you never query for Subjects outside of the context of a Teacher, you may want to consider embedding the Subject instead of simply storing its identifier. That would remove the need to query for Subjects after the fact.
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