简体   繁体   English

如何使用.net驱动程序2.7.3将新文档同时插入到MongoDB 4中的集合中

[英]how to concurrently insert a new document into a collection in MongoDB 4 with .net driver 2.7.3

I am using MongoDB 4 and MongoDB .net Driver 2.7.3. 我正在使用MongoDB 4和MongoDB .net驱动程序2.7.3。 I want to concurrently insert a new document in a collection so that there should be only one document in the collection. 我想同时在一个集合中插入一个新文档,以便该集合中应该只有一个文档。 After the document is inserted into the collection (that is, set sequenceValue as 1), we only need to update the document (that is, increase sequenceValue by 1), instead of inserting any more new document. 将文档插入到集合中之后(即,将sequenceValue设置为1),我们只需要更新文档(即,将sequenceValue增加1),而不用再插入任何新文档。

In this collection named 'countersCollection', the structure of the new document is a Counter class like: 在这个名为“ countersCollection”的集合中,新文档的结构是一个Counter类,例如:

    public class Counter
    {
        [BsonId]
        public ObjectId _id { get; set; }
        public string name { get; set; }
        public long sequenceValue {get;set;}
        public DateTime date {get;set;}
    }

my code is like: 我的代码是这样的:

    Counter c = await this.countersCollection.AsQueryable().FirstOrDefaultAsync(x => x.name == counterName);                
    DateTime utcNow = DateTime.UtcNow;                                                                            
    if (c == null) // empty document
    {
        var options = new FindOneAndUpdateOptions<Counter, Counter>() {ReturnDocument = ReturnDocument.After, IsUpsert = true };            
        var filter = new FilterDefinitionBuilder<Counter>().Where(x => x.name == counterName);
        var update = new UpdateDefinitionBuilder<Counter>().Set(x => x.sequenceValue, 1).Set(x => x.date, utcNow);                                        
        seq = await this.countersCollection.FindOneAndUpdateAsync<Counter>(filter, update, options);                    
    }

The above code works well in a non-concurrent environment but does not work well concurrently. 上面的代码在非并发环境中运行良好,但不能同时运行。 If multiple threads call the above code at the same time, it could create more than one counter document in the countersCollection. 如果多个线程同时调用上述代码,则可能在countersCollection中创建多个计数器文档。

Is there any way to make it work concurrently. 有什么办法可以使其同时工作。

Thank you. 谢谢。

you can handle it by using optimistic concurrency approach 您可以使用乐观并发方法来处理

the algo can be like: 算法可以像:

  1. you find Counter with the name == counterName 你发现Countername == counterName
  2. build your filter like var filter = new FilterDefinitionBuilder<Counter>().Where(x => x.name == counterName && x.sequenceValue == c.sequenceValue ); var filter = new FilterDefinitionBuilder<Counter>().Where(x => x.name == counterName && x.sequenceValue == c.sequenceValue );一样构建您的过滤var filter = new FilterDefinitionBuilder<Counter>().Where(x => x.name == counterName && x.sequenceValue == c.sequenceValue );
  3. try to find and update and if the result is null retry 尝试查找和更新,如果结果为null重试

Ok. 好。 Let`s write some code. 让我们写一些代码。

  1. create separate exception for OptimisticConcurrency 为OptimisticConcurrency创建单独的异常

     public class OptimisticConcurrencyException : Exception { } 
  2. create Counter class 创建Counter

     public class Counter { [BsonId] public ObjectId Id { get; set; } public string Name { get; set; } public long Version { get; set; } public DateTime Ddate { get; set; } } 
  3. create some simple retry logic 创建一些简单的重试逻辑

      public class CounterRepository { private readonly IMongoCollection<Counter> _countersCollection; public CounterRepository(IMongoCollection<Counter> countersCollection) { _countersCollection = countersCollection ?? throw new ArgumentNullException(nameof(countersCollection)); } public async Task<Counter> TryInsert(Counter counter) { var policy = Policy.Handle<OptimisticConcurrencyException>() .WaitAndRetryAsync(new[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(7) }); return await policy.ExecuteAsync(() => TryInsertInternal(counter)); } private async Task<Counter> TryInsertInternal(Counter counter) { var existingCounter = await _countersCollection.Find(c => c.Id == counter.Id).FirstOrDefaultAsync(); if (existingCounter == null) return await InsertInternal(counter); return await IncreaseVersion(existingCounter); } private async Task<Counter> InsertInternal(Counter counter) { try { counter.Version = 1; await _countersCollection.InsertOneAsync(counter); return counter; } // if smbd insert value after you called Find(returns null at that moment) // you try to insert entity with the same id and exception will be thrown // you try to handle it by marking with optimistic concurrency and retry policy // wait some time and execute the method and Find returns not null now so that // you will not insert new record but just increase the version catch (MongoException) { throw new OptimisticConcurrencyException(); } } private async Task<Counter> IncreaseVersion(Counter existing) { long versionSnapshot = existing.Version; long nextVersion = versionSnapshot + 1; var updatedCounter = await _countersCollection.FindOneAndUpdateAsync( c => c.Id == existing.Id && c.Version == versionSnapshot, new UpdateDefinitionBuilder<Counter>().Set(c => c.Version, nextVersion)); // it can be null if smbd increased existing version that you fetched from db // so you data is now the newest one and you throw OptimisticConcurrencyException if (updatedCounter == null) throw new OptimisticConcurrencyException(); return updatedCounter; } } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM