【问题标题】:Mongoose scheme design many-to-manyMongoose 模式设计多对多
【发布时间】:2019-06-12 20:14:27
【问题描述】:

我正在尝试为任务管理应用程序提出一个猫鼬方案架构/设计。

所需的功能和模型:

型号:

  • 用户 - 有看板
  • Boards - 有列表
  • 列表 - 有卡片
  • 卡片

功能:

  • 用户可以创建板
  • 用户可以在版块中创建列表
  • 用户可以在列表中创建卡片
  • 用户可以将成员添加到董事会
  • 用户可以从董事会成员向卡片添加成员
  • 用户可以删除版块,删除所有列表和卡片,删除成员
  • 用户可以删除列表,删除列表中的所有卡片
  • 用户可以删除卡,从卡中删除所有成员
  • 用户可以更新板子中的列表位置(排序)
  • 用户可以更新卡片在列表中的位置(排序)

  • 附加功能(可选)

    • 用户可以将 cmets 添加到卡中
    • 卡片活动记录(移动、编辑、评论)

我知道有很多问题要问,但是如何为这种功能设计方案呢?

子父引用会是最好的解决方案吗?

例如

用户:

{
 name: 'Bob',
 _id: '2626'
}

董事会:

{
 name: 'Board name',
 _id: '123456',
 members: [
  { type: ObjectID, ref: 'user' } // '2626'
 ]
}

列表:

{
 name: 'List name',
 _id: '2525',
 boardId: { type: ObjectID, ref: 'board' } // '123456'
}

卡片:

{
 name: 'Card name',
 boardId: { type: ObjectID, ref: 'board' } // '123456',
 listId: { type: ObjectID, ref: 'list' } // '2525'
}

我将如何查询这种类型的结构?

  • 1) 按用户 id 获取板块列表
  • 2) 按板 id 获取列表列表
  • 3) 通过board id & listid 获取卡片列表

所以对于棋盘视图,我会去获取所有列表,但随后我必须去获取每个列表的所有卡片,这似乎效率不高。

也许在进入棋盘视图时,我应该只通过棋盘 ID 查询卡片,但是我如何获取列表,并将每张卡片放入自己的列表中?

如何删除卡片或将卡片从一个列表移动到另一个列表?

请不要对我太苛刻,我真的是 mongodb 世界的新手,但我真的很努力。

【问题讨论】:

  • 你能展示一下预期的输出吗
  • 这就是我想要弄清楚的。我正在尝试在前端呈现一个带有列表和卡片的类似 trello 的板。

标签: mongodb mongoose database-design


【解决方案1】:

您定义的架构非常好,流程如下。

最初,当用户登录时,您需要向他们显示板列表。这应该很容易,因为您只需使用 board 集合上的 user_id 进行查找查询。

Board.find({members: user_id}) // where user_id is the ID of the user

现在当用户点击特定的板子时,您可以获取带有 board_id 的列表,类似于上面的查询。

List.find({boardId: board_id}) // where board_id is the ID of the board

同样,您可以借助 list_id 和 board_id 获得卡片。 Card.find({boardId: board_id, listId: list_id}) // 其中 board_id 是板子的 ID,listId 是列表的 ID

现在,让我们看看您可能同时需要来自 2 个或更多集合的数据的情况。 例如,当用户点击板时,您不仅需要板中的列表,还需要板中的卡片。 在这种情况下,您需要编写这样的聚合,

            Board.aggregate([
            // get boards which match a particular user_id
            {
                $match: {members: user_id}
            },
            // get lists that match the board_id
            {
                $lookup:
                {
                    from: 'list',
                    localField: '_id',
                    foreignField: 'boardId',
                    as: 'lists'
                }
            }
        ])

这将返回棋盘,在每个棋盘中,都会有一个与该棋盘关联的列表数组。如果一个特定的板没有列表,那么它将有一个空数组。

同样,如果您想将卡片添加到列表和板中,聚合查询将是一个更复杂的机器人,因此,

            Board.aggregate([
            // get boards which match a particular user_id
            {
                $match: {members: user_id}
            },
            // get lists that match the board_id
            {
                $lookup:
                {
                    from: 'list',
                    localField: '_id',
                    foreignField: 'boardId',
                    as: 'lists'
                }
            },
            // get cards that match the board_id
            {
                $lookup:
                {
                    from: 'card',
                    localField: '_id',
                    foreignField: 'boardId',
                    as: 'cards'
                }
            }
        ])

这也将添加一系列卡片。同样,您也可以获取列表卡片。

现在,让我们考虑一下这是否是最佳架构。我个人认为您建议的架构非常好,因为另一种方法是将 ID 存储在父集合中,这将让您使用填充来获取数据而不是查找查询。

例如,将列表 ID 存储在板集合中。这样做的缺点是,每当添加新列表时,您需要将该列表添加到列表集合中,并且还需要更新列表所连接的板(添加列表 ID),我认为这太繁琐了。

最后,关于您给出的架构的一些建议, 我认为您应该在每个集合中添加 user_id(创建者 ID),因为在很多情况下,您需要显示创建该特定板或列表或其他任何内容的用户的名称,而且因为您具有将用户添加到的功能一张特定的卡片等我认为你应该有两个字段,一个是 creator_id,另一个应该是 associated_users,这将是一个数组(显然你可以选择更好的名称)。

您应该在卡片和其他要按位置排序的集合中添加位置字段。该字段应该是一个数字。

现在删除一张卡片或将其从一个列表移动到另一个列表应该非常简单且不言自明。

修改 1:基于评论
您不需要在聚合“之后”将卡片分配给列表,您可以在聚合本身中执行此操作,所以它会是这样的,

Board.aggregate([
    // get boards which match a particular user_id
    {
        $match: { members: user_id }
    },
    // get lists that match the board_id
    {
        $lookup:
        {
            from: 'list',
            localField: '_id',
            foreignField: 'boardId',
            as: 'lists'
        }
    },
    // unwind lists (because it's an array at the moment)
    {
        $unwind: '$lists'
    },
    // Now you have object named lists in every board object
    // get cards that match the list_id (note that the cards contain list_id)
    {
        $lookup:
        {
            from: 'card',
            localField: '_id',
            foreignField: 'listId',
            as: 'cards'
        }
    },
    // now merge back the objects and get a simple object for each boardID
    {
        $group: {
            _id: "$_id",
            members: { $addToSet: "$members" },
            lists: { $addToSet: "$lists" }
        }
    }
])

这会给你这样的东西,

data = {
    '_id': '123456',
    'members': [
        {
            name: 'Bob',
            _id: '2626'
        },
        {
            name: 'Matthew',
            _id: '2627'
        }
    ],
    'lists': [
        {
            name: 'List 1',
            _id: '2525',
            boardId: '123456',
            cards: [
                {
                    name: 'Card 1',
                    boardId: '123456',
                    listId: '2525'
                },
                {
                    name: 'Card 2',
                    boardId: '123456',
                    listId: '2525'
                }
            ]
        },
        {
            name: 'List 2',
            _id: '2526',
            boardId: '123456',
            cards: [
                {
                    name: 'Card 3',
                    boardId: '123456',
                    listId: '2525'
                },
                {
                    name: 'Card 4',
                    boardId: '123456',
                    listId: '2525'
                }
            ]
        }
    ]
}

所以基本上,您可以在单个查询中获取列表和这些列表的卡片,这非常有效。

现在来到你要求的两个查询,

  1. 卡片从一个列表移动到另一个列表,只需将卡片文档中的 listId 字段编辑为新的 listID(实际上很简单)。

  2. 卡片在列表中上移了一个位置

正如我所说,如果您想要位置,则需要在文档中添加一个名为 position 的字段,然后每当移动卡片时,您都需要更改这些卡片的“位置”值。 在聚合中,您只需要添加另一个名为 '$sort' 的阶段并根据位置值对其进行排序。 这会有点乏味,因为每当您将卡片向上移动时,您也需要更新上方卡片的位置。

【讨论】:

  • 多么出色的答案,感谢您花时间深入解释一切。许多荣誉!
  • Altho,我仍然有点迷失在聚合之后如何将卡片分配给列表。另外 - 这种聚合不会我必须通过 id 查找列表然后查找所有具有另一个查找的列表卡会导致多个查询和一般的低性能?谁能给我两个示例查询 - 1 - 卡片从一个列表移动到另一个列表,2 - 卡片在列表中移动了一个位置 奖励 Visrozar 和任何可以解释我要求的两个查询的人的声誉。谢谢!
  • 编辑了答案以适应评论中的问题,请参阅编辑 1
猜你喜欢
  • 1970-01-01
  • 2021-09-13
  • 2019-09-25
  • 2015-06-27
  • 1970-01-01
  • 2012-03-01
  • 2012-05-09
  • 2018-08-19
  • 1970-01-01
相关资源
最近更新 更多