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
我可能晚了一个答案,但我有同样的问题,发现了这个问题,但没有真正的答案如何解决和为什么会发生。
发生的事情是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
{
...
}