Search code examples
arraysmongodbmorphia

UpdateOperations embedded array


I have an structure like this:

{
  _id: 123,
  bs: [
    {
      _id: 234,
      cs: [
        {
          _id: 456,
          ds : [
            {
              _id: 678,
              emails[
                "[email protected]"
              ]
            }
          ]
        }
      ]
    }
  ]
}

My classes in Morphia seems like this

@Entity
public class A {

  @Id
  private ObjectId id;

  @Embedded
  private List<B> bs;
}

public class B {

  private ObjectId id;

  @Embedded
  private List<C> cs;
}

public class C {

  private ObjectId id;

  @Embedded
  private List<D> ds;
}

public class D {

  private ObjectId id;

  @Embedded
  private List<String> emails;
}

What I am trying to do is insert an email inside embedded array with Morphia without retrieve all element A and use updateFirst.

This is the query I am trying execute

Query<Event> query = this.basicDAO.createQuery();
query.criteria(Mapper.ID_KEY).equal(new ObjectId(aID));
query.criteria("bs.cs.id").equal(new ObjectId(cID));
query.criteria("bs.cs.ds.id").equal(dID);

UpdateOperations<Event> updateOps = this.basicDAO.createUpdateOperations().set("bs.cs.ds.$.emails", email);

this.basicDAO.update(query, updateOps);

I also read about this post Update an item in an array that is in an array with said that

$ operator does not work "with queries that traverse nested arrays".

So I tried something like:

D d = new D(dID);
C c = new C(new ObjectId(cID));

Query<Event> query = this.basicDAO.createQuery();
query.criteria(Mapper.ID_KEY).equal(new ObjectId(aID));
query.field("bs.cs").hasThisElement(c);
query.field("bs.cs.ds").hasThisElement(d);

UpdateOperations<Event> updateOps = this.basicDAO.createUpdateOperations().set("bs.cs.ds.emails", email);

this.basicDAO.update(query, updateOps);

However it still doesn't work. Any idea how solve this? The error message that I receive is cannot use the part ... to transverse the element


Solution

  • Based on your stand-in use case, I think you should "invert" your schema. Each document will represent a lecture and will be tagged with its theme, edition, and event:

    {
        "_id" : ObjectId("54da1ff0a9ce603a239c3075"),
        "event" : "X0004",
        "edition" : "A0002,
        "theme" : "RT0005",
        "votes" : 22
    }
    

    event, edition, and theme can be some kind of identifiers, and might be references to event, edition, and theme documents in other collections. To cast a vote for a particular lecture, just update it by _id:

    db.test.update({ "_id" : ObjectId("54da1ff0a9ce603a239c3075") }, { "$inc" : { "votes" : 1 } })
    

    While I don't know your full requirements, I think this is a better basic design given your example use case.