qq_42724979 2024-12-04 00:06 采纳率: 50%
浏览 37
已结题

Typegoose 中如何使用 arrayFilters 筛选并更新深度嵌套的子文档数组信息

请教站内各位专家:Typegoose 中如何使用 arrayFilters 筛选并更新深度嵌套的子文档数组信息

技术栈:NestJs + Typegoose

以下为:TS 类型定义及Typegoose 配置代码


@modelOptions({
  schemaOptions: {
    collection: 'xs',
    strict: 'throw',
    toJSON: { virtuals: true },
    toObject: { virtuals: true },
  },
})
export class X {
  @prop({ required: true })
  public _id!: Types.ObjectId;

  @prop()
  public name!: string;

  @prop({
    type: () => Array<Y>,
  })
  public ys: Array<Y>;
}

export class Y {
  @prop({ required: true })
  public _id!: Types.ObjectId;

  @prop()
  public name!: string;

  @prop({
    type: () => Array<Z>,
  })
  public zs: Array<Z>;

}

export class Z {
  @prop({ required: true })
  public _id!: Types.ObjectId;

  @prop()
  public name!: string;
}

以下为:Mongodb 数据库中的数据


x: {
  _id: {
        "$oid": '666a71e0e338f61c15dca0b4'
  },
  name: '...',
  ys: [{
    _id: {
        "$oid": '674d7e35ddbff7015c1dc6b6'
    },
    name: '...',
    zs: [{
      _id: {
         "$oid": '674f0131e6ebadbddd7636ac'
      },
      name: '...',
    }]
  }]
}

以下为:Typegoose 导入模型


import { ReturnModelType } from '@typegoose/typegoose';

constructor(@InjectModel(X) private readonly ModuleX: ReturnModelType<typeof X>) {}

我的第一个需求是:通过主文档 x、子文档ys的ID 号,修改子文档的名称(代码执行正确)


await this.ModuleX.findOneAndUpdate(
  { _id: new mongodb.Types.ObjectId('666a71e0e338f61c15dca0b4') },
  { $set: { 'ys.$[y].name': 'name' } },
  {
    arrayFilters: [
      { 'y._id': new mongodb.Types.ObjectId('674d7e35ddbff7015c1dc6b6') } }
    ],
  },
);

我的第二个需求是:通过主文档 x、子文档ys 和孙文档zs 的ID 号,修改孙文档的名称

await this.ModuleX.findOneAndUpdate(
  { _id: new mongodb.Types.ObjectId('666a71e0e338f61c15dca0b4') },
  { $set: { 'ys.$[y].zs.$[z].name': 'name' } },
  {
    arrayFilters: [
      { 'y._id': new mongodb.Types.ObjectId('674d7e35ddbff7015c1dc6b6') }, { 'z._id': new mongodb.Types.ObjectId('674f0131e6ebadbddd7636ac') }
    ],
  },
);

// 以上代码报以下错误:
[Nest] 19956  - 12/03/2024, 12:37:04 PM   ERROR [ExceptionsHandler] Could not find path "ys.0.zs.0._id" in schema

注意:经检查,确认以下条件

  1. 数据路径实际是存在的,也就是 ys.0.zs.0._id 路径是正确的。因为以下代码在 Mongodb Compass 命令行中执行是正确的

    db.xs.findOneAndUpdate(
     { _id: new mongodb.Types.ObjectId('666a71e0e338f61c15dca0b4') },
     { $set: { 'ys.$[y].zs.$[z].name': 'xxx' } },
     { arrayFilters: [{ 'y._id': new mongodb.Types.ObjectId('674d7e35ddbff7015c1dc6b6') }, { 'z._id': new mongodb.Types.ObjectId('674f0131e6ebadbddd7636ac') }] },
    );
    
  2. 使用以下方式,也可以执行成功

    await this.ModuleX.collection.findOneAndUpdate(
     { _id: new mongodb.Types.ObjectId('666a71e0e338f61c15dca0b4') },
     { $set: { 'ys.$[y].zs.$[z].name': 'xxx' } },
     { arrayFilters: [{ 'y._id': new mongodb.Types.ObjectId('674d7e35ddbff7015c1dc6b6') }, { 'z._id': new mongodb.Types.ObjectId('674f0131e6ebadbddd7636ac') }] },
    );
    
  3. 只有以下方式不能成功

    await this.ModuleX.findOneAndUpdate(
     { _id: new mongodb.Types.ObjectId('666a71e0e338f61c15dca0b4') },
     { $set: { 'ys.$[y].zs.$[z].name': 'name' } },
     { arrayFilters: [ { 'y._id': new mongodb.Types.ObjectId('674d7e35ddbff7015c1dc6b6') }, { 'z._id': new mongodb.Types.ObjectId('674f0131e6ebadbddd7636ac') }] },
    );
    
  4. 第一个需求中的代码已经证明了,通过 ys ID 单一条件可以正确的找到并修改

  5. Typegoose 已经升级到最新版本:v 12.9.1

  • 写回答

30条回答 默认 最新

  • 问答-莫得感情 2024-12-04 00:08
    关注

    ️题主您好️,目前由我为您解答👇🏼
    🧡参考谷歌智能助手,由吾为汝整理提供🧡

    问题在于 Typegoose 的 schema validation 机制,它在进行更新操作时会尝试验证更新路径的有效性。对于深度嵌套的数组,它无法正确解析 ys.$[y].zs.$[z].name 这样的路径,因为它包含了多个位置运算符 $[]。这就是为什么你会收到 "Could not find path" 的错误,即使路径在数据库中实际存在。

    解决方案:

    1. 绕过 Typegoose 的 Schema Validation:

    最简单的解决方案是绕过 Typegoose 的 schema validation,直接使用 MongoDB 驱动程序进行更新操作。

    import { MongoClient } from 'mongodb';
    
    constructor(
      @InjectModel(X) private readonly ModuleX: ReturnModelType<typeof X>,
      @Inject(MONGO_CLIENT_PROVIDER) private readonly mongoClient: MongoClient,
    ) {}
    
    async updateNestedDocument() {
      const collection = this.mongoClient.db().collection('xs'); // 获取集合
    
      await collection.findOneAndUpdate(
        { _id: new mongodb.Types.ObjectId('666a71e0e338f61c15dca0b4') },
        { $set: { 'ys.$[y].zs.$[z].name': 'name' } },
        {
          arrayFilters: [
            { 'y._id': new mongodb.Types.ObjectId('674d7e35ddbff7015c1dc6b6') },
            { 'z._id': new mongodb.Types.ObjectId('674f0131e6ebadbddd7636ac') },
          ],
        },
      );
    }
    
    // 在 app.module.ts 中导入 MongoClient
    @Module({
      imports: [
        MongooseModule.forRootAsync({
          useFactory: async () => ({
            uri: 'your_mongodb_uri',
          }),
        }),
        MongooseModule.forFeature([X]),
      ],
      providers: [
        {
          provide: MONGO_CLIENT_PROVIDER,
          useFactory: async (mongoose: Mongoose) => mongoose.connection.getClient(),
          inject: [Mongoose],
        },
      ],
    })
    export class AppModule {}
    
    1. 分两步更新:

    可以将更新操作拆分为两步:

    • 首先,使用 findById 查找文档。
    • 然后,手动遍历 ys 数组,找到匹配的 yz,并直接修改 name 属性。最后保存更新后的文档。
    async updateNestedDocument() {
      const x = await this.ModuleX.findById('666a71e0e338f61c15dca0b4');
    
      if (x) {
        const yId = new mongodb.Types.ObjectId('674d7e35ddbff7015c1dc6b6');
        const zId = new mongodb.Types.ObjectId('674f0131e6ebadbddd7636ac');
    
        const yIndex = x.ys.findIndex(y => y._id.equals(yId));
    
        if (yIndex !== -1) {
          const zIndex = x.ys[yIndex].zs.findIndex(z => z._id.equals(zId));
    
          if (zIndex !== -1) {
            x.ys[yIndex].zs[zIndex].name = 'name';
            await x.save();
          }
        }
      }
    }
    
    

    方法比较:

    • 方法 1 更简洁高效,直接利用 MongoDB 的原生功能。
    • 方法 2 更加 Typegoose 化,但效率略低,需要两次数据库操作。

    建议使用第一种方法,因为它更直接,性能更好,并且避免了 Typegoose schema validation 的限制。 记得安装 mongodb 包: npm install mongodb

    希望以上信息能帮到你! 如果还有其他问题,请继续提问。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(29条)

报告相同问题?

问题事件

  • 系统已结题 12月14日
  • 已采纳回答 12月6日
  • 修改了问题 12月4日
  • 创建了问题 12月4日