【问题标题】:MongoDB C# driver - Change Id serialization for inherited classMongoDB C# 驱动程序 - 更改继承类的 Id 序列化
【发布时间】:2015-07-20 21:44:33
【问题描述】:

我已经为我的集合实现了带有基实体类的存储库模式。到目前为止,所有集合都有 _idObjectId 类型。在代码中,我需要将Id 表示为一个字符串。

EntityBase 类如下所示

public abstract class EntityBase
{
    [BsonRepresentation(BsonType.ObjectId)]
    public virtual string Id { get; set; }
}

这是映射:

BsonClassMap.RegisterClassMap<EntityBase>(cm =>
{
    cm.AutoMap();
    cm.MapIdProperty(x => x.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
    cm.IdMemberMap.SetSerializer(new StringSerializer().WithRepresentation(BsonType.ObjectId));
});

现在我有一个语言集合,它的 ID 将是纯字符串,例如 en-GB

{
   "_id" : "en-GB",
   "Term1" : "Translation 1",
   "Term2" : "Translation 2"
}

Language 类继承 EntityBase

public class Language : EntityBase
{
    [BsonExtraElements]
    public IDictionary<string, object> Terms { get; set; }
    public override string Id { get; set; }
}

问题是我能否以某种方式更改Id 仅针对Language 类的序列化方式?

我不想更改 EntityBase 类的行为,因为我有很多其他集合继承了 EntityBase

更新

这是我尝试并得到异常的方法。不确定我尝试过的是否可行。

BsonClassMap.RegisterClassMap<Language>(cm =>
{
    cm.AutoMap();
    cm.MapExtraElementsMember(c => c.Terms);
    cm.MapIdProperty(x => x.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
    cm.IdMemberMap.SetSerializer(new StringSerializer().WithRepresentation(BsonType.String));
});

这是我得到的例外:

An exception of type 'System.ArgumentOutOfRangeException' occurred in MongoDB.Bson.dll but was not handled in user code

Additional information: The memberInfo argument must be for class Language, but was for class EntityBase.

at MongoDB.Bson.Serialization.BsonClassMap.EnsureMemberInfoIsForThisClass(MemberInfo memberInfo)
at MongoDB.Bson.Serialization.BsonClassMap.MapMember(MemberInfo memberInfo)
at MongoDB.Bson.Serialization.BsonClassMap`1.MapMember[TMember](Expression`1 memberLambda)
at MongoDB.Bson.Serialization.BsonClassMap`1.MapProperty[TMember](Expression`1 propertyLambda)
at MongoDB.Bson.Serialization.BsonClassMap`1.MapIdProperty[TMember](Expression`1 propertyLambda)
at Test.Utilities.MongoDbClassConfig.<>c.<Configure>b__0_1(BsonClassMap`1 cm) in F:\Development\Test\Utilities\MongoDbClassConfig.cs:line 23
at MongoDB.Bson.Serialization.BsonClassMap`1..ctor(Action`1 classMapInitializer)
at MongoDB.Bson.Serialization.BsonClassMap.RegisterClassMap[TClass](Action`1 classMapInitializer)
at Test.Utilities.MongoDbClassConfig.Configure() in F:\Development\Test\Utilities\MongoDbClassConfig.cs:line 20
at Test.Portal.BackEnd.Startup..ctor(IHostingEnvironment env) in F:\Development\Test\Startup.cs:line 43

【问题讨论】:

  • 也许这是一种幼稚的做法。但是您能否在您的语言类中再次实现 Id 属性(覆盖基类的属性)。然后只为这个语言类添加一个特定的映射,并以你喜欢的方式处理序列化?
  • 这是我尝试的第一件事。我遇到了一个异常,即 Id 属于 EntityBase 类。
  • 你不能创建 ID 属性 virtual 并为 Id 的类型使用通用类型参数。然后在派生类中覆盖它? --- 而且,你需要使用[BsonId]-attribute,让它将Id识别为BSON-id。当您覆盖您的虚拟属性时,您的 [BsonRepresentation(BsonType.ObjectId)] 将不会被继承。

标签: c# mongodb mongodb-.net-driver


【解决方案1】:

我可能迟到了答案,但我遇到了同样的问题,找到了这个问题,但没有真正的答案如何解决以及为什么会发生。

发生的情况是 Mongo 驱动程序没有明确理解多态性,您必须手动提及每种类型。所有继承自同一个类的类型都必须列出并解释给BsonClassMap

在您定义 EntityBase 的情况下,您可以列出所有将被序列化的子项,它会使其正常工作。所以大致如下:

[BsonDiscriminator(RootClass = true)]
[BsonKnownTypes(typeof(Language), typeof(OtherEntity))]
public abstract class EntityBase
{
    [BsonId]
    public virtual string Id { get; set; }
}

应该可以。 就我而言,我想要一个通用的实体保存方法,而我的实体不知道 Mongo 驱动程序,所以我不能使用属性。我在 EntityBase 中有我的 ID,所以这是一个简单的通用 add 方法(只是相关但有效的部分):

public async Task Add<T>(T item) where T : EntityBase, new()
{
    BsonClassMap.RegisterClassMap<EntityBase>(cm =>
    {
        cm.AutoMap();
        cm.MapIdMember(p => p.Id);
        cm.SetIsRootClass(true);
    });
    BsonClassMap.RegisterClassMap<T>();

    await Db.GetCollection<T>(typeof(T).Name).InsertOneAsync(item);
}

如您所见,我必须将基类映射到每个条目。然后对象被正确序列化并保存在数据库中。

【讨论】:

  • 不幸的是,这迫使您在域层中引用 MongoDB,这是不行的
【解决方案2】:

当您使用属性[BsonRepresentation(BsonType.ObjectId)] 时,您是在告诉序列化程序,即使这是一个字符串——您希望它由ObjectId 类型表示。 "en-GB" 不是有效的ObjectId,因此序列化程序会抛出异常。

如果您想指定名为Id 的属性必须是您的集合中的唯一标识符,请使用[BsonId]-属性。

驱动程序中还有一个ObjectId-data 类型,您可以实现它。

我会为您的EntityBase-class 添加一个通用类型参数。

public abstract class EntityBase<TId>
{
    [BsonId]
    public TId Id { get; set; }
}

您也可以实现EntityBase-class,而无需像这样的泛型类型参数:

public abstract class EntityBase : EntityBase<ObjectId>
{
}

那么对于你的Language-class

public class Language : EntityBase<string>
{
    [BsonExtraElements]
    public IDictionary<string, object> Terms { get; set; }
}

驱动程序将ObjectId 的字符串表示形式序列化为ObjectId 应该没有任何问题;但我会先尝试一下——只是为了确保。

【讨论】:

  • 这看起来很有希望,我们将尝试一下!谢谢。
  • 试一试;告诉我结果! :)
  • 你对上面的@zoranpro @Matthias有什么好运吗?
  • 我明天去测试一下
  • 我实现了它,但还没有完全改变我的 Web API 控制器并对其进行测试。至少它可以编译并且对我来说非常有意义。到时候我也会测试一下。但我会提前接受你的回答。然后我会告诉你结果。顺便说一句:我查看了您的 GitHub 存储库。你有有趣的 MongoDB 项目。
【解决方案3】:

我刚刚在这个问题上花费了几个小时并且被难住了 - 但我现在有了解决方案! Gurgen 的答案的问题是 SuperBaseEntity.Id 始终为 null - 因为 Id 属性已被覆盖。我已经做到了:

public abstract class Base
{
  [BsonIgnore]
  public abstract string Id {get;set}
}
public class ClassWithStringId : Base
{
  [BsonElement("_id")]
  [BsonId]
  public override string Id {get;set;}
}
public classs ClassWIthOidId : Base
{
  [BsonElement("_id")]
  [BsonId]
  public override string Id {get;set;}
}
...
//Class Map where the magic happens
BsonClassMap.RegisterClassMap<Base>(o =>
{
  o.SetIsRootClass(true);
  o.AutoMap();
}
BsonClassMap.RegisterClassMap<ClassWithOidId>(o =>
{
  o.AutoMap();
  //o.SetDiscriminator("...")//you probably need this
  o.MapIdProperty("Id"); //!!!! This is the solution! You need to reference the Id Property via string instead of the normal Expression c => c.Id, the latter will lead to an exception: "MemberInfo was for Type Base but not for Type XYZ"
  o.IdMemberMap.SetSerializer(new StringSerializer().WithRepresentation(BsonType.YourFavoriteType));
}

【讨论】:

    【解决方案4】:

    看看这个解决方案。对我来说它有效。 为每个基类设置BsonRepresentation 并像这样忽略来自SuperBaseEntityId 字段,并为从SuperBaseEntity 派生的每个ID 类型基类创建

    public class SuperBaseEntity
    {
        [BsonIgnore]
        public string Id { get; set; }
    }
    
    public class ObjectIdBaseEntity : SuperBaseEntity
    {
        [BsonRepresentation(BsonType.ObjectId)]
        public new string Id
        {
            get => base.Id;
            set => base.Id = value;
        }
    }
    
    public class StringIdBaseEntity : SuperBaseEntity
    {
        [BsonRepresentation(BsonType.String)]
        public new string Id
        {
            get => base.Id;
            set => base.Id = value;
        }
    }
    
    public class Language : StringIdBaseEntity
    {
        [BsonExtraElements]
        public IDictionary<string, object> Terms { get; set; }
    }
    
    public class AllOtherEntities : ObjectIdBaseEntity
    {
        ...
    }
    

    【讨论】:

    • 你能解释一下为什么你定义了 Id virtual 然后使用 new 修饰符。完全不清楚你为什么在什么场景下做这一切。
    • 你说得对,SuperBaseEntity中的Id没必要设置成虚拟的。我会修改答案,谢谢。
    • 使用 new 关键字也是一种糟糕设计的味道
    • 你有别的办法吗?让我知道我会改变答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多