我在获取要按属性投影集合属性而不是从 MongoDB 中的 bson 文档中投影整个字段时遇到了一些麻烦。
以下是
Evt
集合中的文档示例:
[
{
"Inner": {
"RootName" : "FooBar",
"RootExtra" : "abcdefghijlmnopqrstuvwxyz",
"Items": [
{
"Val" : 1,
"Name" : "Foo",
"ExtraProperty" : "abcdefghijlmnopqrstuvwxyz",
"InnerItems" : [
{
"InnerVal" : "Importante thing",
"InnerExtra" : "Unimportant thing"
}
]
},
{
"Val" : 2,
"Name" : "Bar",
"ExtraProperty" : "abcdefghijlmnopqrstuvwxyz",
"InnerItems" : [
{
"InnerVal" : "Importante thing",
"InnerExtra" : "Unimportant thing"
}
]
}
],
"Singular" : {
"Prop1" : "123ABC",
"Prop2" : "ABC!@#",
"Prop3" : "Unwanted"
}
}
}
]
这是我的 C# 类模型。值得一提的是,我的模型仅用于读取目的,也就是说写入是由另一个应用程序完成的。此外,文档是非规范化的,因此,我的模型仅包含我绝对确定将在我映射它们的位置的属性。
[DisplayName("Evt")]
public class Evt1
{
public InnerEvt1 Inner { get; set; }
}
public class InnerEvt1
{
public List<EvtItem1> Items { get; set; }
public string RootName { get; set; }
public Singular Singular { get; set; }
}
public class EvtItem1
{
public int Val { get; set; }
public int Name { get; set; }
public List<InnerItem> InnerItems { get; set; }
}
public class InnerItem
{
public int InnerVal { get; set; }
public string InnerName { get; set; }
}
public class Singular
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
如您所见,我在模型中遗漏了一些属性(例如
RootExtra
、InnerExtra
、ExtraProperty
)。所有这些(文档示例和模型)都只是示例,因为在我的实际情况中,我在模型中遗漏了许多沉重的属性,也就是说我不希望它们加载到我的模型中。查询。
如果我只是使用
检索整个集合database.GetCollection<Evt1>("Evt").AsQueryable()
我的投影看起来像这样:
databaseName.SchemaName.Evt.Aggregate(
[
{
"$match": {
"Disabled": {
"$ne": true
}
}
},
{
"$project": {
"_id": "$_id",
"Evt": "$Evt",
}
}
]
)
这意味着在构建模型之前整个文档将被加载到内存中并丢弃剩余/额外的属性。这在性能上会非常糟糕。
因此,我使用 Automapper 为我创建投影 linq/查询:
CreateMap<Evt1, Evt1>();
CreateMap<InnerEvt1, InnerEvt1>();
CreateMap<EvtItem1, EvtItem1>();
CreateMap<InnerItem, InnerItem>();
CreateMap<Singular, Singular>();
查询本身看起来像这样:
database.GetCollection<Evt1>("Evt").AsQueryable().ProjectTo<Evt1>(mapper.ConfigurationProvider);
这使得我的投影看起来像这样:
[
{
"$match": {
"Disabled": {
"$ne": true
}
}
},
{
"$project": {
"_id": "$_id",
"Inner": {
"$cond": {
"if": {
"$eq": [
"$Inner",
null
]
},
"then": null,
"else": {
"Items": "$Evt.Items",
"_id": "$Evt._id",
"RootName": "$Evt.RootName",
"Singular": {
"$cond": {
"if": {
"$eq": [
"$Evt.Singular",
null
]
},
"then": null,
"else": {
"Prop1": "$Evt.Singular.Prop1",
"Prop2": "$Evt.Singular.Prop2"
}
}
}
}
}
}
}
}
]
对于类
Inner
(RootName
) 和奇异引用类型属性 (Singular
) 中的值类型属性,投影按预期工作,换句话说,投影其各个值,而不是检索整个块,因此,不会从数据库中获取不需要的值。
问题在于集合属性 (
Items
) 的行为,即使已添加其地图,该集合属性仍被投影为块。
我尝试了几种替代方案,包括:
List<EvtItem1>
映射到 List<EvtItem1>
ForMember(a => a.Items, opts.MapFrom(a.Items))
EvtItem
中创建自定义构造函数,因此每个属性都会单独初始化唯一有效的选择是第四个。这是复制的模型:
[DisplayName("Evt")]
public class Evt2
{
public InnerEvt2 Inner { get; set; }
}
public class InnerEvt2
{
public List<EvtItem2> Items { get; set; }
public string RootName { get; set; }
public Singular Singular { get; set; }
}
public class EvtItem2
{
public int Val { get; set; }
public int Name { get; set; }
public List<InnerItem> InnerItems { get; set; }
}
地图:
CreateMap<EvtBasesTrab, EvtBasesTrab2>();
CreateMap<EvtBasesTrabRoot, EvtBasesTrabRoot2>();
CreateMap<InfoCp, InfoCp2>();
查询:
database.GetCollection<Evt1>("Evt").AsQueryable().ProjectTo<Evt2>(mapper.ConfigurationProvider);
还有投影:
[
{
"$match": {
"Disabled": {
"$ne": true
}
}
},
{
"$project": {
"_id": "$_id",
"Inner": {
"$cond": {
"if": {
"$eq": [
"$Inner",
null
]
},
"then": null,
"else": {
"Items": {
"$map": {
"input": "$Inner.Items",
"as": "dtoItems",
"in": {
"Val": "$$dtoItems.Val",
"Name": "$$dtoItems.Name",
"InnerItems": "$$dtoItems.InnerItems"
}
}
},
"_id": "$Evt._id",
"RootName": "$Evt.RootName",
"Singular": {
"$cond": {
"if": {
"$eq": [
"$Evt.Singular",
null
]
},
"then": null,
"else": {
"Prop1": "$Evt.Singular.Prop1",
"Prop2": "$Evt.Singular.Prop2"
}
}
}
}
}
}
}
}
]
主要区别在于
Items
属性的属性是单独映射的,这正是我的意图。问题是,在现实世界中,有太多的类需要重复,所以这个解决方案是不可行的。
有没有人有更好的解决方案?
我相信,无论您如何实现应用程序代码,数据库引擎将始终加载文档(及其所有字段),然后仅过滤到您已投影的字段。您可能需要重新访问您的架构并遵循 MongoDB(和文档数据库)架构设计的最佳实践。