MongoDB c#驱动-更改继承类的Id序列化

本文关键字:Id 序列化 继承 驱动 MongoDB | 更新日期: 2023-09-27 18:03:35

我已经为我的集合实现了带有基实体类的Repository模式。到目前为止,所有的集合都是ObjectId类型的_id。在代码中,我需要将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

MongoDB c#驱动-更改继承类的Id序列化

我可能晚了一个答案,但我有同样的问题,发现了这个问题,但没有真正的答案如何解决和为什么会发生。

发生的事情是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,所以这里有一个简单的通用添加方法(只是相关的,但工作的部分):

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);
}
如您所见,我必须将基类映射到每个条目。然后,对象被适当地序列化并保存在数据库中。

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

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

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

我会给你的EntityBase类添加一个泛型类型参数。

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

您还可以实现EntityBase -类,而不需要泛型类型参数:

public abstract class EntityBase : EntityBase<ObjectId>
{
}

然后是Language -class

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

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

我刚刚花了几个小时在这个问题上,被难住了-但我现在有解决方案!!Gurgen回答的问题是SuperBaseEntity.Id总是空的——因为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));
}

看看这个解决方案。对我来说,这很有效。为每个基类设置BsonRepresentation,并像这样忽略SuperBaseEntity中的Id字段,并为每个从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
{
    ...
}