Rotem Varon
Rotem Varon

Reputation: 1647

Insert a document while auto incrementing a sequence field in MongoDB

Problem statement:

I have a collection in MongoDB that has a field with the type Int32. I would like to add a document to this collection. I need to increment the value by 1 for each insert as that field is indexed and must be unique.

Options:

  1. [preferable] Increment the value on the DB side. That is, not specifying a new (higher) value. Just instruct MongoDB to auto increment upon insert.
  2. Reading first. Executing a find query against the DB to find the current (before insert) highest value first, incrementing in memory, and inserting the new doc. This might fail due to racing conditions (the operation is not atomic).
  3. keeping an index counter in memory. Not an option for me as there are multiple apps writing to the same collection (legacy limitation).
  4. Other Ideas?

Example:

{
    _id: ....
    index: 123,
    open: true
}

 await collection.InsertOneAsync(record.ToBsonDocument());

The new doc inserted should have index value of 124

Language:

C#

Questions:

Can you provide a sample code (C#) to achieve the first option?

Extra info:

I do not have access to the code of the other app (which keeps its own index number). So having another collection and adding an sequence resolver function will not work as this will trigger a change to the legacy app.

Upvotes: 4

Views: 11017

Answers (3)

Tiago Ávila
Tiago Ávila

Reputation: 2867

I had to do this in a project using MongoDB C# Driver.

Here's what I did: I created a separated collection called Sequence, with the name and value of it and I also created a repository for it.

Here is the code of class Sequence:

public class Sequence
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    [BsonElement("_id")]
    public string Id { get; set; }

    public string SequenceName { get; set; }

    public int SequenceValue { get; set; }
}

And now the code of the method to generate the sequence value:

public class SequenceRepository
{
    protected readonly IMongoDatabase _database;
    protected readonly IMongoCollection<Sequence> _collection;

    public SequenceRepository(IMongoDatabase database)
    {
        _database = database;
        _colecao = _database.GetCollection<Sequence>(typeof(Sequence).Name);
    }

    public int GetSequenceValue(string sequenceName)
    {
        var filter = Builders<Sequence>.Filter.Eq(s => s.SequenceName, sequenceName);
        var update = Builders<Sequence>.Update.Inc(s => s.SequenceValue , 1);

        var result = _colecao.FindOneAndUpdate(filter, update, new FindOneAndUpdateOptions<Sequence, Sequence> { IsUpsert = true, ReturnDocument = ReturnDocument.After });

        return result.SequenceValue;
    }
}

Finally I called this method before insert some document:

public void Inserir(Order order)
{
    order.Code = new SequenceRepository(_database).GetSequenceValue("orderSequence");

    _collection.InsertOne(order);
}

Upvotes: 1

Adriano Martins
Adriano Martins

Reputation: 1808

MongoDB has a default tutorial on how to achieve that here

1 - Create a counters collections and insert the id there:

db.counters.insert(
   {
      _id: "userid",
      seq: 0
   }
)

2 - Create a custom function to retrieve the next value:

function getNextSequence(name) {
   var ret = db.counters.findAndModify(
          {
            query: { _id: name },
            update: { $inc: { seq: 1 } },
            new: true
          }
   );

   return ret.seq;
}

Use the getNextSequence to retrieve the next value:

db.users.insert(
   {
     _id: getNextSequence("userid"),
     name: "Sarah C."
   }
)

db.users.insert(
   {
     _id: getNextSequence("userid"),
     name: "Bob D."
   }
)

Upvotes: 2

Amit Phaltankar
Amit Phaltankar

Reputation: 3424

You can create a Mongo Sequence in a separate collection counter

db.counter.insert({ _id: "mySeq", seq: 0 })

You can encapsulate sequence logic in a simple function like this

function getNextMySeq(name) {
    var ret = db.counter.findAndModify({
        query: { _id: name },
        update: { $inc: { seq: 1 } },
        new: true
    });
    return ret.seq;
}

Now simply use the function call during the insert

db.collection.insert({
  index: getNextMySeq("mySeq") 
})

Upvotes: 0

Related Questions