При таком определении тестового документа:
public record TestDocument(string Id, int Version, string SomeDataString);
Мы должны ожидать, что:
- добавление документа с Id="Foo" и Version=[anything] работает если в коллекции еще нет Id="Foo", вставка нового документа
- добавление документа с Id="Foo" и Version=2 работает, если есть Id="Foo" документ с версией 1 (все, что меньше 2), замена существующего документа
- добавление документа с Id="foo" и Version=2 ничего не дает, если есть Id="Foo" документ с версией 2 или выше
var idMatchFilter = Builders.Filter.Eq(e => e.Id, upsertDocument.Id);
var versionFilter = Builders.Filter.Lt(e => e.Version, upsertDocument.Version);
var combinedFilter = Builders.Filter.And(idMatchFilter, versionFilter);
var replaceOptions = new ReplaceOptions() { IsUpsert = true };
try
{
await testCollection.ReplaceOneAsync(combinedFilter, upsertDocument, replaceOptions);
}
catch (MongoWriteException mongoWriteException) when (mongoWriteException.Message.Contains("A write operation resulted in an error. WriteError: { Category : \"DuplicateKey\", Code : 11000, Message : \"E11000 duplicate key error collection"))
{
// expected possible error when we attempt to insert an older document - ideally this would be a no-op instead of attempted write
}
Однако, поскольку фактический вариант использования представляет собой массовое обновление (скажем, 1000 документов) гораздо более сложного документа (неважно, где мы могли бы просто установить одно или два свойства, нам нужно заменить документ), то это не кажется хорошим подходом, если это вообще осуществимо. Это также противоречит Azure Cosmo DB, хотя я надеюсь, что это не будет иметь смысла, и все, что работает с обычным mongodb, должно работать и здесь.
Вот тест xunit, который включает в себя некоторые из вещи, которые я пробовал, и почему они до сих пор терпели неудачу. Обратите внимание, что в том виде, в каком оно написано, оно проходит, но только потому, что мы перехватываем это исключение MongoWriteException, и цель состоит в том, чтобы выполнить операцию, которая просто не выполняет никаких операций, а не пытается записать документ старой версии.
[Theory]
// insert, nothing persisted yet
[InlineData(null, 1, true)]
// update, persisted version is older
[InlineData(1, 2, true)]
// update, persisted version is same
[InlineData(2, 2, false)]
// update, persisted version is newer
[InlineData(2, 1, false)]
public async Task ConditionalUpsert(int? persistedVersion, int upsertVersion, bool shouldUpdate)
{
// arrange
var testCollection = GetTestCollection();
var emptyFilter = Builders.Filter.Empty;
await testCollection.DeleteManyAsync(emptyFilter);
if (persistedVersion.HasValue)
{
// seed the collection with expected version
var testDocument = new TestDocument("Foo", persistedVersion.Value, "persisted document dummy payload");
await testCollection.InsertOneAsync(testDocument);
}
var upsertDocument = new TestDocument("Foo", upsertVersion, "new document dummy payload");
// act
var idMatchFilter = Builders.Filter.Eq(e => e.Id, upsertDocument.Id);
var versionFilter = Builders.Filter.Lt(e => e.Version, upsertDocument.Version);
var combinedFilter = Builders.Filter.And(idMatchFilter, versionFilter);
// incorrectly updates the persisted document when trying to write older version
//var findOneAndReplaceOptions = new FindOneAndReplaceOptions { IsUpsert = true };
//await testCollection.FindOneAndReplaceAsync(idMatchFilter, upsertDocument, findOneAndReplaceOptions);
// incorrectly tries to insert new document insert of no-op when upserting an old version
// Command findAndModify failed: E11000 duplicate key error collection: SomeDatabase.SomeCollection. Failed _id or unique index constraint
//var findOneAndReplaceOptions = new FindOneAndReplaceOptions { IsUpsert = true };
//await testCollection.FindOneAndReplaceAsync(combinedFilter, upsertDocument, findOneAndReplaceOptions);
var replaceOptions = new ReplaceOptions() { IsUpsert = true };
try
{
// incorrectly tries to insert new document with older version since the specified filter doesn't find the persisted version if it was newer
await testCollection.ReplaceOneAsync(combinedFilter, upsertDocument, replaceOptions);
}
catch (MongoWriteException mongoWriteException) when (mongoWriteException.Message.Contains("A write operation resulted in an error. WriteError: { Category : \"DuplicateKey\", Code : 11000, Message : \"E11000 duplicate key error collection"))
{
// expected possible error when we attempt to insert an older document - ideally this would be a no-op instead of attempted write
}
// assert
var allDocuments = await testCollection.Find(emptyFilter).ToListAsync();
var queriedDocument = Assert.Single(allDocuments);
var expectedVersion = shouldUpdate ? upsertVersion : persistedVersion;
Assert.Equal(expectedVersion, queriedDocument.Version);
}
Подробнее здесь: https://stackoverflow.com/questions/790 ... -collectio