繁体   English   中英

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

[英]MongoDB C# driver - Change Id serialization for inherited class

我已经为我的集合实现了带有基本实体类的 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));
});

现在我有一个 Language 集合,它的 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; }
}

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

我不想更改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

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

发生的事情是 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);
}

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

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

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

驱动程序中还有一个ObjectId数据类型,您可以改为实现它。

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

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

您还可以实现一个EntityBase -class,而没有这样的泛型类型参数:

public abstract class EntityBase : EntityBase<ObjectId>
{
}

然后为您的Language

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

司机不应该有任何麻烦,序列化的字符串表示ObjectIdObjectId ; 但我会先试一试——只是为了确定。

我刚刚在这个问题上花了几个小时并被难住了 - 但我现在有了解决方案!! 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));
}

看看这个解决方案。 对我来说它奏效了。 为每个基类设置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
{
    ...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM