Reputation: 12024
I have user and photo documents in Mongodb. Each photo belongs to user and a photo maybe shared among users. Lets say user1 has p1,p2,p3 photos and user2 has p3,p4,p5 photos. If I delete user1 (manually using tools like Compass), p1 and p2 should also be deleted but not p3. How to achieve this and what kind of database structure I need to define?
Currently if I delete user1, no photos are deleted and remain in databse which now makes the database corrupted from the point of view of the application using the database.
Its Spring Boot app and User and Photo are declared as:
import lombok.Builder;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
@Document
@Data
@Builder
public class User {
@Id
private String id;
@DBRef
private Set<Photo> photos;
private String name;
}
@Document
@Data
@Builder
public class Photo {
@Id
private String id;
private String fileName;
}
Upvotes: 24
Views: 25113
Reputation: 897
Here is a generic python implementation for cascade delete. We assume that the 'foreign key' is an ObjectId of the parent.
The enter method is called when the context is entered, and it automatically invokes the discover_collections method to find collections with ObjectId references. The exit method is called when the context is exited, and it closes the MongoDB client connection.
Using the class as a context manager ensures that the MongoDB connection is properly managed, and the discover_collections method is called at the appropriate time.
Remember to replace <mongodb_connection_string> with your actual MongoDB connection string and 'your_database_name' with the name of your database.
Call the class within a context:
with CascadeDelete('<mongodb_connection_string>', 'your_database_name') as cascade_delete:
cascade_delete.delete(ObjectId('parent1'))
The CascadeDelete class implementation:
from pymongo import MongoClient
from bson.objectid import ObjectId
class CascadeDelete:
""" Usage:
with CascadeDelete('<mongodb_connection_string>', 'your_database_name') as cascade_delete:
cascade_delete.delete(ObjectId('parent1'))
"""
def __init__(self, connection_string, database_name):
self.client = MongoClient(connection_string)
self.db = self.client[database_name]
self.collections = []
def __enter__(self):
self.discover_collections()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.client.close()
def discover_collections(self):
for collection_name in self.db.list_collection_names():
collection = self.db[collection_name]
if self._has_objectid_references(collection):
self.collections.append(collection)
def delete(self, document_id):
deleted_documents = set()
self._delete_documents(document_id, deleted_documents)
for collection in self.collections:
collection.delete_many({'_id': {'$in': list(deleted_documents)}})
def _delete_documents(self, document_id, deleted_documents):
deleted_documents.add(document_id)
for collection in self.collections:
document = collection.find_one({'_id': document_id})
if document:
for key, value in document.items():
if isinstance(value, ObjectId) and key != '_id':
self._delete_documents(value, deleted_documents)
@staticmethod
def _has_objectid_references(collection):
sample_document = collection.find_one()
if not sample_document:
return False
for value in sample_document.values():
if isinstance(value, ObjectId):
return True
return False
I hope this can help someone...
Kind regards
Upvotes: 0
Reputation: 457
@mindcraft is right, but if you wanted to keep photos in separate collection then you can add access property to the photo
document like
{
ref: 'https://....',
access:[user1._id, user2._id]
}
You can then query like -
db.photos.find({access:{$in:[user1._id]}})
Although separate collection specifically for photos will not help a lot. Instead try putting photo urls in array
Upvotes: 0
Reputation: 477
As mentioned by m4gic and in the questions he linked (here and here), MongoDB doesn't support cascading deletes. In your situation you should probably create an array in the User object, and put the complete child documents into that array instead of keeping them in their own collection. That way they will be deleted together with the parent, because they are a part of it.
Upvotes: 15
Reputation: 139
MongoDB doesn't support for cascade delete as of now. As you are already storing the ref photos in the User model, you can get the photo ids from the reference list and delete the photos together. Or instead of storing the photos in the separate collection you can have the array of Photos embedded into the user object.
You can refer to this link as well: What is the recommended equivalent of cascaded delete in MongoDB for N:M relationships?
Upvotes: 3