在我的应用程序中,我有一个名为Parameter
的POCO类,它从.csv文件导入并存储在mongodb集合中。由于POCO类用于我的存储库项目之外的项目,我不希望使用ObjectId作为Id,这将使得必须在整个地方添加对MongoDb的引用,其中应该没有。现在,有两种不同类型的参数,非数字和数字。前者有一个唯一的代码,可能看起来像这个A013
或这个104
,后者没有,所以我想我可能会使用非数字参数的代码作为Id和嗯,只是任何东西对于数字。最后,为了简单起见,我试图获得一个Guid的字符串表示(哈!),不必管理任何数字或任何东西。但是,我很难让它发挥作用。
所以这是结果:
public class Parameter : INotifyPropertyChanged
{
public string Id { get; set; }
public string Name { get; set; }
public string Unit { get; set; }
public ParameterType ParameterType { get; set; }
}
在导入数值参数期间会发生这种情况:
.Select((columnName, index) => new Parameter()
{
Name = columnName,
Unit = columnUnits[index],
ParameterType = ParameterType.TestResult
})
.ForEach(parameter =>
{
parameterCollection.ReplaceOne(Builders<Parameter>.Filter
.Eq("Name", parameter.Name),
parameter,
new UpdateOptions()
{
IsUpsert = true
});
});
使用Parameter类可以看到atop导致导入时出现异常:
MongoDB.Driver.MongoWriteException:'写操作导致错误。 E11000重复键错误集合:timeSeriesDb.Parameters index: id dup key:{:null}'
笨拙地,MongoDb将Id识别为Id,但只是插入空值而不是抱怨Id不能为空。因此,异常是合乎逻辑的,因为没有参数具有id。
所以我尝试使用自定义id生成器:
[BsonId(IdGenerator = typeof(StringGuidGenerator))]
public string Id { get; set; }
生成器是这样的:
public class StringGuidGenerator : IIdGenerator
{
public object GenerateId(object container, object document)
{
return Guid.NewGuid().ToString();
}
public bool IsEmpty(object id)
{
return id == null;
}
}
但是,导入仍会抛出完全相同的错误,因为永远不会调用生成器。根据{{3}}问题[BsonIgnoreIfDefault]
缺失,但在尝试接收数据时添加会导致错误,因为MongoDb现在只是忽略了Id字段和生成器并添加了自己的默认值ObjectId到插入的对象,然后无法转换回来。
return this.MongoDatabase.GetCollection<Parameter>("Parameters")
.Find(Builders<Parameter>.Filter.Empty)
.ToList();
System.FormatException:'反序列化类TSI.Data.DTO.Parameter的Id属性时发生错误:无法从BsonType'ObjectId'反序列化'String'。'
如果我在将数据添加到文档之前自己创建了Id,那么它可以正常工作。但是,如果我第二次尝试导入相同的.csv文件,我将收到以下异常:
.Select((columnName, index) => new Parameter()
{
Id = Guid.NewGuid().ToString(),
Name = columnName,
Unit = columnUnits[index],
ParameterType = ParameterType.TestResult
})
.ForEach(/* blabla replaceone blabla */)
MongoDB.Driver.MongoWriteException:'写操作导致错误。 应用更新后,发现(不可变)字段'_id'已被更改为_id:“05e94906-ab03-4dd1-b953-29fab850e267”'
这显然意味着MongoDb实际上并没有在调用ReplaceOne时删除和插入,但实际上以某种方式尝试更改有问题的条目,就好像我调用了UpdateOne,因此尝试将不可变Id更改为新值。哇......我甚至都没有...
好的,我也尝试了下面的内容,它确实有用......对于没有预定义Id的数字参数。幸运的是,非数字参数来自预定义的查找表,实际上没有导入,否则我猜这会导致更多异常。它确实创建了一个MongoDb ObjectId的字符串表示,而不是我想要的Guid,但只要这不会给其他地方带来麻烦,我就可以了。
public class Parameter : INotifyPropertyChanged
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonIgnoreIfDefault]
public string Id { get; set; }
...
但是,我真的很想知道为什么我的自定义id生成器不起作用,我需要做些什么才能让它工作?还有UpdateOne和ReplaceOne有什么区别?
答案 0 :(得分:1)
嗯,所有问题的根源在于,关键字段(Id
)实际上并不扮演密钥的角色:在更新期间,您不匹配此字段的项目(匹配{{1并且每次更新对象时都会为此字段生成新值,这会导致预期的问题。
最后你得到了一个可行的解决方案,但这个模型是怎样的
Name
满足您原来的要求:
...我不希望将ObjectId用作Id,这将成为它 有必要在所有地方添加对MongoDb的引用,其中 应该没有。
所有这些属性都在MongoDB.Bson库中定义,它仍然会将模型耦合到MongoDB。
从您的导入逻辑来看,似乎public class Parameter : INotifyPropertyChanged
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonIgnoreIfDefault]
public string Id { get; set; }
// ...
}
是Name
模型的真正关键。否则,基于Parameter
过滤器替换整个文档没有多大意义。
在这种情况下,最好的选择是删除syntetic Name
属性并将Id
指定为id字段。
最简单的方法是将Name
重命名为Parameter.Name
。在这种情况下,代码中唯一剩下的更改是更新过滤器中的字段名称:
Parameter.Id
如果您希望将此属性保留为Builders<Parameter>.Filter.Eq(x => x.Id, parameter.Id);
,则可以通过使用Name
属性标记属性来实现此目的:
BsonId
或者,如果您希望保持模型的原始要求不与MongoDB库耦合,则可以使用类映射。在第一次调用public class Parameter
{
[BsonId]
public string Name { get; set; }
// ...
}
集合之前调用以下映射:
Parameter
我认为这是一种更清洁的方法,可以解决您当前的错误,并在将来保护您免受其他潜在问题的影响。
如果您仍然感兴趣,以下是其他问题的答案:
...为什么我的自定义ID生成器不起作用,我该怎么做 让它工作?
将新文档插入集合时,BsonClassMap.RegisterClassMap<Parameter>(cm =>
{
cm.AutoMap();
cm.MapIdMember(x => x.Name);
});
将完美无缺。但是在文档更新期间仅使用StringGuidGenerator
。原因是MongoDB中不允许更新id字段。因此,您应该在传递更新的文档中具有正确填充的ID,或者使用IdGenerator
标记id字段,以便不使用update命令传递Id字段。但是,如果文档不存在,则后续方案将不适用于upsert。不幸的是,MongoDB .Net Driver在发送upsert命令时无法知道文档是否存在。这就是为什么BsonIgnoreIfDefault
不能很好地与upserts一起使用。
... UpdateOne和ReplaceOne有什么区别?
调用IdGenerator
方法时,您需要提供完整的替换文档。所有文档字段都将使用新文档字段进行更新。
ReplaceOne
方法允许更具体的文档更改。您提供了一组要更新的字段。不会触及所有其他文档字段。以下是UpdateOne
方法调用的示例:
UpdateOne