简体   繁体   中英

Working with embedded documents in MongoDB

I have a document with the following structure, some fields omitted for brevity;

{
  _id: 1,
  projectName: name,
  managers: [
    { managerId: manager1, status: false, startDate: startDate, endDate: endDate },
    { managerId: manager2, status: false, startDate: startDate, endDate: endDate }
    { managerId: manager3, status: true, startDate: startDate, endDate: endDate }
  ]
}

Here are my business rules;

  • A project can be created with/without being assigned a manager
  • A manager can be assigned/re-assigned a project - projects are transferable between managers
  • Each project should have a footprint of the managers involved
  • A project cannot have more than one managers at the same time

My intention is;

  • Upon re-assigning an officer, set the current active manager's status to false then push a new manager into the embedded document using a single query because it makes the most sense making a single round trip to the database unlike two round trips. The following is a snippet of what I came up with;

     Collection.update( { _id: 1, "managers.status": true }, { $set: { "managers.$.status": false }, $push: { managers: { managerId: newManagerId, status: true, startDate: startDate, endDate: endDate } } } );

Challenges

My intention becomes a challenge because of the following circumstances;

  • When a new project is created without a manager, the managers field which contains the embedded managers documents is not set

  • How do I check whether an embedded document with the status of true exists so that I can set it to false and also if it doesn't push a new document in

Any ideas?

UPDATE

With reference to @Matt K's response, it made sense to make some better design decisions on my schema and made use of unshift instead of push. I'm also setting the officers field default to an empty array on insert incase no manager is assigned yet. Following is how my schema looks like after restructuring it;

    {
      _id: 1,
      projectName: name,
      managers: [
        { managerId: manager1, startDate: startDate },
        { managerId: manager2, startDate: startDate }
        { managerId: manager3, startDate: startDate }
      ]
    }

And here's my query;

    Collection.update(
      { _id: 1 },
      {
        $push: {
           managers: {
              $each: [{ managerId: newManagerId, startDate: startDate }],
              $position: 0
           }
         }
       }
    );

Note that Mongo API doesn't provide unshift as an atomic operation but there's $position which achieves the same effect. So I no longer need status because all current managers will be at position 0 of the array. Also I don't need endDate because I can easily get that from a previous manager's document. Hope this helps someone :)

In keeping with your current schema (not recommended):

First, save the entire document to the client:

doc = {
  _id: 1,
  projectName: name,
  managers: [
    { managerId: "manager1", status: false, startDate: "startDate", endDate: "endDate" },
    { managerId: "manager2", status: false, startDate: "startDate",endDate: "endDate" },
    { managerId: "manager3", status: true, startDate: "startDate", endDate: "endDate" }
  ]
}

Add a managers field if it doesn't exist:

doc.managers = doc.managers || [];

Then, iterate over each field, setting status to false:

for (var i = 0; i < doc.managers.length; i++) {
  doc.managers[i].status = false;
}

Push the new manager:

doc.managers.push({
  managerId: "newManagerId", status: true, startDate: "startDate", endDate: "endDate"
})

Update the doc with your new one:

Collection.update(1, doc)

OR, if you're really, really concerned with sending over a couple extra bytes:

Collection.update(1, {$set: {managers: doc.managers}})

After all that, I recommend you for ditch the status field in your managers array & create a new field called currentManager . Doing so does a few things:

  • it eliminates the effort you have to put in to keep the status fields mutually exclusive

  • it makes finding the current manager VERY easy (no $elemMatch )

  • it makes more sense, since your PK of the managers subdoc isn't the manager, rather a joint key of managerId, startDate, and endDate.

Alternatively, get rid of the status field & unshift new managers instead of pushing. Then, you know field 0 always has the current (if one exists).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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