MongoDb自定义Id生成器不会被调用

时间:2018-03-16 16:23:22

标签: c# mongodb

在我的应用程序中,我有一个名为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有什么区别?

1 个答案:

答案 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