[英]how to concurrently insert a new document into a collection in MongoDB 4 with .net driver 2.7.3
我正在使用MongoDB 4和MongoDB .net驱动程序2.7.3。 我想同时在一个集合中插入一个新文档,以便该集合中应该只有一个文档。 将文档插入到集合中之后(即,将sequenceValue设置为1),我们只需要更新文档(即,将sequenceValue增加1),而不用再插入任何新文档。
在这个名为“ 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;}
}
我的代码是这样的:
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);
}
上面的代码在非并发环境中运行良好,但不能同时运行。 如果多个线程同时调用上述代码,则可能在countersCollection中创建多个计数器文档。
有什么办法可以使其同时工作。
谢谢。
您可以使用乐观并发方法来处理
算法可以像:
Counter
与name == counterName
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 );
null
重试 好。 让我们写一些代码。
为OptimisticConcurrency创建单独的异常
public class OptimisticConcurrencyException : Exception { }
创建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; } }
创建一些简单的重试逻辑
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.