mongo-dart / mongo_dart

Mongo_dart: MongoDB driver for Dart programming language
https://pub.dev/packages/mongo_dart
MIT License
445 stars 98 forks source link

make the 'insert' and 'update' methods not modify the past Map #236

Open insinfo opened 3 years ago

insinfo commented 3 years ago

make the 'insertOne' and 'replaceOne' ... methods not modify the past Map, to prevent the error, "MongoError: The (immutable) field '_id' was found to have been altered"

  final cupom = Cupom.fromMap(req.bodyAsMap);
  cupom.setInfoByLang('pt');
  final repository = CupomRepository();
  cupom.quantidade = cupom.quantidade - 1;
  final map = cupom.toMap();
  var result = await repository.createCupomUsado(map);
  //to prevent the error, "MongoError: The (immutable) field '_id' was found to have been altered"
  map.remove('_id');    
  result = await repository.updateById(map, cupom.id);

I verified that the insertOne method was putting the "_id" in the Map which was causing me problem

giorgiofran commented 3 years ago

Yes, insertOne adds an _id field if not specified. Why should it give you a problem?

insinfo commented 3 years ago

this caused me a problem, because I do an insertion and then an update of the same map in different collections, the first collection being a history of changes, and the second collection is the main one, but when I go to save in the second collection the error is due to that the first insert modifies the Map by putting an _id

insinfo commented 3 years ago

maybe a solution for this would be the insertOne method to clone the Map so that it doesn't modify the original

giorgiofran commented 3 years ago

Let's say that I have a map (myMap):<String, dynamic>{'name': 'Isaque', 'familyName':'Neves} Now I do the first insert in a collection, let's say history. As MongoDb requires an _id field we have two ways of set the '_id': 1) before insertion create an ObjectId (or any key you like) and assign it to the map:

var documentKey = ObjectId();
myMap['_id'] = documentKey;
await history.insertOne(myMap);

This way I have the map ready to be inserted in another collection (actual)

await actual.insertOne(myMap);

If you need to update a document on actual simply use the map you have

actual.replaceOne( where.id(myMap['_id']),  myMap);

or

actual.replaceOne( where.id(documentKey),  myMap);

2) I you do not want to insert manually the _id field, insertOnewill do this for you. The result is that myMap will have the_id set and you will be able to use it for the update.

So, filling the _id field in insertOne is intentional and useful.

giorgiofran commented 3 years ago

Sorry, if I cannot help you anymore, but it is still unclear to me what you would like to do. If you could post an example without using your framework it could be useful.

insinfo commented 3 years ago

Let us assume this situation:

I have a Map so it's also stored in the 'coupons' collection :

var data = {"id":123456,"name":"Isaque", "age":36}

I want to be included in the 'usedCoupons' collection

  await db.collection('usedCoupons').insertOne(data);

and then I want to update the 'coupons' collection

await db.collection( 'coupons').replaceOne({'id':123456}, data);

This code will fail as the insertOne method added an '_id' to Map data

A possible solution would be the insertOne method to clone the variable data like this

final Map<String, dynamic> documentClone = {...document};
giorgiofran commented 3 years ago

I do not know if it was a typo or not but beware that "id" and "_id" are two different things for mongoDb. MongoDb manages an "_id" field as unique key identifying a specific document inside a collection. This field must be present, mongo_dart adds it for you, if you omit it, but if the driver don't insert it the database server itself adds one. If it is not a typo, this is why your code is failing. The correct sequence should be:

var data = {"_id":123456,"name":"Isaque", "age":36}

If the map have been inserted in a collection it should contain an "_id" field.

await db.collection('usedCoupons').insertOne(data);

Now we insert the data map inside the "usedCoupons" collection with the same _id as the "coupons" one. As the _id field is already present, it is not changed.

data['counter']++;
await db.collection( 'coupons').replaceOne({'_id':123456}, data);

No we can update the "coupons" collection using the unique identifier _id.

Hope this can help.

insinfo commented 3 years ago

I don't think you understand, I don't use the "_id" field, because I have my own method of generating unique "id", so I use an "id" field instead of "_id", my solution was to make a wrapper on top of the driver so that the insertion into the database is done without changing the Map passed via parameter, this was done easily through the spreed operator cloning the Map, by design I think the driver should not modify the instance of Map passed by parameter, it should clone the Map and modify the clone and not the original instance. my problem is not mongo putting the "_id" field, the problem is the driver modifying the Map instance passed via parameter

giorgiofran commented 3 years ago

What I do not understand is why you do not use the _id field if you already have an unique identifier. The _id field can be of any type you like, not necessarily an ObjectId.