我具有下面的类结构。我试图通过仅传递对象的一部分来调用UpdateAsync。由于某种原因,它仅在根对象级别TestObject类上尊重BsonIgnoreIfDefault,而在TestProduct上则不这样做。
public class TestObject
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonIgnoreIfDefault]
public string Id { get; set; }
[Required]
public string KoId { get; set; }
[BsonIgnoreIfDefault]
public string Summary { get; set; }
public TestProduct Product { get; set; }
}
public class TestProduct
{
[BsonIgnoreIfDefault]
public string Name { get; set; }
[BsonIgnoreIfDefault]
public List<string> Skus { get; set; }
}
这是我的集成测试的摘要:
public async Task EndToEndHappyPath()
{
const string summary = "This is a summary";
var obj = new TestObject
{
Summary = summary,
KoaId = "1234",
Product = new TestProduct
{
Name = "laptop",
Skus = new List<string>
{
"Memory"
}
}
};
// CREATE
await _mongoAsyncRepository.CreateAsync(obj);
obj = new TestObject
{
KoaId = koaId,
Description = description,
Product = new TestProduct
{
Skus = new List<string>
{
"RAM"
}
}
};
// UPDATE
var response = await _mongoAsyncRepository.UpdateAsync(koaId, obj);
response.ShouldBeTrue();
// RETRIEVE
result = await _mongoAsyncRepository.RetrieveOneAsync(koaId);
testObject = (result as TestObject);
testObject.Product.ShouldNotBeNull();
// this is failing; Name value is null in MongoDb
testObject.Product.Name.ShouldBe("laptop");
testObject.Product.Skus.ShouldNotBeNull();
testObject.Product.Skus.Count.ShouldBe(1);
testObject.Product.Skus[0].ShouldBe("RAM");
}
public async Task<bool> UpdateAsync(string id, T obj)
{
try
{
_logger.Log(new KoaLogEntry(KoaLogLevel.Debug, $"Attempting to update a {typeof(T)} {id} document."));
//var actionResult = await GetMongoCollection()?.ReplaceOneAsync(new BsonDocument("KoaId", id), obj);
var updated = new BsonDocument
{
{
"$set", bsonDoc
}
};
UpdateDefinition<BsonDocument> updatedObj = UpdateBuilder.DefinitionFor(updated);
var actionResult = await GetMongoCollection()?.UpdateOneAsync(new BsonDocument("KoaId", id), updated);
_logger.Log(new KoaLogEntry(KoaLogLevel.Debug, $"Updated a {typeof(T)} {id} document. IsAcknowledged = {actionResult.IsAcknowledged}; ModifiedCount = {actionResult.ModifiedCount}"));
return actionResult.IsAcknowledged
&& actionResult.ModifiedCount > 0;
}
catch (Exception exc)
{
_logger.Log(new KoaLogEntry(KoaLogLevel.Error, exc.Message, exc));
throw;
}
}
private readonly IMongoClient _client;
protected IMongoCollection<T> GetMongoCollection()
{
var database = _client.GetDatabase(this.DatabaseName);
return database.GetCollection<T>(typeof(T).Name);
}
由于某种原因,尽管我已经将BsonIgnoreIfDefault属性放在了Name上,但Name却被覆盖为null。
请让我知道我所缺少的。 谢谢 阿伦
答案 0 :(得分:0)
我做了一些研究,似乎不支持此功能。
BsonIgnoreIfDefault的意思是“如果默认,则不包括在db的文档中”,并不意味着“忽略更新”。
您的更新命令
var actionResult = await GetMongoCollection()?.UpdateOneAsync(new BsonDocument("KoaId", id), updated);
应具有与此相同的行为:
await GetMongoCollection().ReplaceOneAsync(_ => _.KoaId == id, obj);
它将替换现有文档。
文档说(我假设c#驱动程序没有任何作用):
如果文档仅包含field:value表达式,则: update()方法用文档替换匹配的文档。 update()方法不会替换_id值。有关示例,请参阅替换所有字段。
https://docs.mongodb.com/manual/reference/method/db.collection.update/
因此,您正在执行替换操作,所有具有默认值的属性都不会写入新的新文档中。
// document after replace without annotations (pseudocode, fragment only)
{
KoaId: "abc",
Summary: null
}
// with Summary annotated with BsonIgnoreIfDefault
{
KoaId: "abc"
}
我找到的唯一解决方案是编写一个可从对象创建UpdateDefinitions并添加自定义属性的生成器。这是我的第一个版本,可能会帮助您起步:
/// <summary>
/// Ignore property in updates build with UpdateBuilder.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class BsonUpdateIgnoreAttribute : Attribute
{
}
/// <summary>
/// Ignore this property in UpdateBuild if it's value is null
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class BsonUpdateIgnoreIfNullAttribute : Attribute
{
}
public static class UpdateBuilder
{
public static UpdateDefinition<TDocument> DefinitionFor<TDocument>(TDocument document)
{
if (document == null) throw new ArgumentNullException(nameof(document));
var updates = _getUpdateDefinitions<TDocument>("", document);
return Builders<TDocument>.Update.Combine(updates);
}
private static IList<UpdateDefinition<TDocument>> _getUpdateDefinitions<TDocument>(string prefix, object root)
{
var properties = root.GetType().GetProperties();
return properties
.Where(p => p.GetCustomAttribute<BsonUpdateIgnoreAttribute>() == null)
.Where(p => p.GetCustomAttribute<BsonUpdateIgnoreIfNullAttribute>() == null || p.GetValue(root) != null)
.Select(p => _getUpdateDefinition<TDocument>(p, prefix, root)).ToList();
}
private static UpdateDefinition<TDocument> _getUpdateDefinition<TDocument>(PropertyInfo propertyInfo,
string prefix,
object obj)
{
if (propertyInfo.PropertyType.IsClass &&
!propertyInfo.PropertyType.Namespace.AsSpan().StartsWith("System") &&
propertyInfo.GetValue(obj) != null)
{
prefix = prefix + propertyInfo.Name + ".";
return Builders<TDocument>.Update.Combine(
_getUpdateDefinitions<TDocument>(prefix, propertyInfo.GetValue(obj)));
}
return Builders<TDocument>.Update.Set(prefix + propertyInfo.Name, propertyInfo.GetValue(obj));
}
}
请不要对性能进行优化。
您可以像这样使用它:
var updateDef = UpdateBuilder.DefinitionFor(updatedDocument);
await Collection.UpdateOneAsync(_ => _.Id == id, updateDef);