Asked  6 Months ago    Answers:  5   Viewed   295 times

Following is my user schema in user.js model -

var userSchema = new mongoose.Schema({
    local: {
        name: { type: String },
        email : { type: String, require: true, unique: true },
        password: { type: String, require:true },
    },
    facebook: {
        id           : { type: String },
        token        : { type: String },
        email        : { type: String },
        name         : { type: String }
    }
});

var User = mongoose.model('User',userSchema);

module.exports = User;

This is how I am using it in my controller -

var user = require('./../models/user.js');

This is how I am saving it in the db -

user({'local.email' : req.body.email, 'local.password' : req.body.password}).save(function(err, result){
    if(err)
        res.send(err);
    else {
        console.log(result);
        req.session.user = result;
        res.send({"code":200,"message":"Record inserted successfully"});
    }
});

Error -

{"name":"MongoError","code":11000,"err":"insertDocument :: caused by :: 11000 E11000 duplicate key error index: mydb.users.$email_1  dup key: { : null }"} 

I checked the db collection and no such duplicate entry exists, let me know what I am doing wrong ?

FYI - req.body.email and req.body.password are fetching values.

I also checked this post but no help STACK LINK

If I removed completely then it inserts the document, otherwise it throws error "Duplicate" error even I have an entry in the local.email

 Answers

91

The error message is saying that there's already a record with null as the email. In other words, you already have a user without an email address.

The relevant documentation for this:

If a document does not have a value for the indexed field in a unique index, the index will store a null value for this document. Because of the unique constraint, MongoDB will only permit one document that lacks the indexed field. If there is more than one document without a value for the indexed field or is missing the indexed field, the index build will fail with a duplicate key error.

You can combine the unique constraint with the sparse index to filter these null values from the unique index and avoid the error.

unique indexes

Sparse indexes only contain entries for documents that have the indexed field, even if the index field contains a null value.

In other words, a sparse index is ok with multiple documents all having null values.

sparse indexes


From comments:

Your error says that the key is named mydb.users.$email_1 which makes me suspect that you have an index on both users.email and users.local.email (The former being old and unused at the moment). Removing a field from a Mongoose model doesn't affect the database. Check with mydb.users.getIndexes() if this is the case and manually remove the unwanted index with mydb.users.dropIndex(<name>).

Tuesday, June 1, 2021
 
revive
answered 6 Months ago
70

The call to mongoose.model establishes the name of the collection the model is tied to, with the default being the pluralized, lower-cased model name. So with your code, that would be 'models'. To use the model with the files collection, change that line to:

var Model = mongoose.model("Model", fileSchema, "files");

or

var Model = mongoose.model("file", fileSchema);
Tuesday, June 1, 2021
 
superhero
answered 6 Months ago
41

You initially had a field called name in your schema, that was set to unique.

How do I know? Because of the error telling me so:

duplicate key error index: **iotdb.users.$name_1**

You renamed the field to username, but didn't remove the old index. By default, MongoDB will set the value of a non-existent field to null in that case.

Relevant documentation here:

If a document does not have a value for the indexed field in a unique index, the index will store a null value for this document. Because of the unique constraint, MongoDB will only permit one document that lacks the indexed field.

To solve this, you need to remove the index for the renamed name field.

Friday, June 25, 2021
 
drowneath
answered 6 Months ago
46

with mongoose in Node.js, can anyone tell me how this above code be reimplemented using the latest Transactions feature

To use MongoDB multi-documents transactions support in mongoose you need version greater than v5.2. For example:

npm install mongoose@5.2

Mongoose transactional methods returns a promise rather than a session which would require to use await. See:

  • Transactions in Mongoose
  • Blog: A Node.JS Perspective on MongoDB 4.0: Transactions

For example, altering the example on the resource above and your example, you can try:

const User = mongoose.model('Users', new mongoose.Schema({
  userId: String, wallet: Number
}));
const Transaction = mongoose.model('Transactions', new mongoose.Schema({
  userId: ObjectId, amount: Number, type: String
}));

await updateWallet(userId, 500);

async function updateWallet(userId, amount) {
  const session = await User.startSession();
  session.startTransaction();
  try {
    const opts = { session };
    const A = await User.findOneAndUpdate(
                    { _id: userId }, { $inc: { wallet: amount } }, opts);

    const B = await Transaction(
                    { usersId: userId, amount: amount, type: "credit" })
                    .save(opts);

    await session.commitTransaction();
    session.endSession();
    return true;
  } catch (error) {
    // If an error occurred, abort the whole transaction and
    // undo any changes that might have happened
    await session.abortTransaction();
    session.endSession();
    throw error; 
  }
}

is not atomic there is always a possibility of user wallet updated with amount but related transaction not created in transactions collection resulting in financial loss

You should also consider changing your MongoDB data models. Especially if the two collections are naturally linked. See also Model data for Atomic Operations for more information.

An example model that you could try is Event Sourcing model. Create a transaction entry first as an event, then recalculate the user's wallet balance using aggregation.

For example:

{tranId: 1001, fromUser:800, toUser:99, amount:300, time: Date(..)}
{tranId: 1002, fromUser:77, toUser:99, amount:100, time: Date(..)}

Then introduce a process to calculate the amount for each users per period as a cache depending on requirements (i.e. per 6 hours). You can display the current user's wallet balance by adding:

  • The last cached amount for the user
  • Any transactions for the user occur since the last cached amount. i.e. 0-6 hours ago.
Saturday, September 4, 2021
 
Abhishek Gupta
answered 3 Months ago
57

I am afraid that this is an ongoing problem. I had the same problem and I found a jira ticket about this:

https://jira.mongodb.org/browse/SERVER-14322

It is possible that two updates come in with upsert:true, resulting in neither finding a document and both inserting new documents which conflict on unique index violations of the query predicate.

The "solution" here is to add a retry code into the client.

Tuesday, September 7, 2021
 
Ace
answered 3 Months ago
Ace
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share