I use MongoDb in C#. Shown below is an object I want to clone, but also I want to merge it into the same collection.
This is a simplified version of the document I use:
class MyDocument
{
public ObjectId Id { get; set; } // my object id. see my question below, type can change
public string MyDocId { get; set; }
public int ContainerId { get; set; } // child of owner so linked
public string OwnerId { get; set; } // this is guid but stored as string for mongodb
}
The object is unique by
- just
Id - combination of
OwnerId + MyDocId + ContainerId, so valid examples of are("12AF", "John", 1),("12AF", "Anna", 1),("12AF", "John", 2),("34AF", "John", 2)and so on, but I cannot add another("12AF", "John", 1), because it already exists.
I want to clone everything of OwnerId+ContainerId 12AF+1 and to 12AF+3. In my example I want to end up having:
("12AF", "John", 1)
("12AF", "Anna", 1)
("12AF", "John", 2)
("34AF", "John", 2)
("12AF", "John", 3)
("12AF", "Anna", 3)
I think I can do this with aggregate and merge operators, no problem.
I actually want to have a different Id field. It is supposed to be a string and it would be as shown here: (Id, OwnerId, MyDocId, ContainerId) format
("12AF\\1:John", "12AF", "John", 1)
("12AF\\1:Anna", "12AF", "Anna", 1)
("12AF\\2:John", "12AF", "John", 2)
("34AF\\2:John", "34AF", "John", 2)
Imagine what I want to do: the cloning of (owner 12AF) container 1 objects to container 3, so I have to update the ID fields too, as shown here:
("12AF\\1:John", "12AF", "John", 1)
("12AF\\1:Anna", "12AF", "Anna", 1)
("12AF\\2:John", "12AF", "John", 2)
("34AF\\2:John", "34AF", "John", 2)
("12AF\\3:John", "12AF", "John", 3)
("12AF\\3:Anna", "12AF", "Anna", 3)
Is this possible without loading all of the objects into my application? (load them into application memory, iterate them to update every ID field and then insert again. this is not what I want)
I would really love to have that ID field like this for lookup in another app where ID fields are set in that format. If I can solve this, then IDs for both apps would match. I cannot change the other application, so I am stuck to my document model.
In SQL this is very simple because we can use string concatenation as shown here, just to give an idea. I want to replicate this with MongoDb.
INSERT INTO MT_TABLE (Id, MyDocId, ContainerId, OwnerId)
SELECT
OwnerId + '\\' + @newContainerId + ':' + MyDocId,
MyDocId, @newContainerId, OwnerId
FROM
MT_TABLE
WHERE
ContainerId = @oldContainerId AND OwnerId = @ownerId
solution 1, without id building
var pipeline = new EmptyPipelineDefinition<MyDocument>()
.Match(Builders<MyDocument>.Filter.Eq(x => x.Container, 1))
.Set(Builders<MyDocument>.SetFields.Set(x => x.Container, 4))
.Project(Builders<MyDocument>.Projection.Exclude(x => x.Id))
.Merge(collection, new MergeStageOptions<MyDocument>
{
WhenMatched = MergeStageWhenMatched.Fail,
WhenNotMatched = MergeStageWhenNotMatched.Insert
});
await collection.AggregateAsync<MyDocument>(pipeline);
solution 2, with id building. concat requires BsonDocument usage
var pipeline = new[]
{
new BsonDocument("$match", new BsonDocument("Container", 1)),
new BsonDocument("$set", new BsonDocument("Container", 4)),
new BsonDocument("$set", new BsonDocument("Path", new BsonDocument("$concat", new BsonArray{ "$Container", ":", "$Name" }))),
new BsonDocument("$project", new BsonDocument("_id", 0)),
new BsonDocument("$merge", new BsonDocument
{
{ "into", "sample" },
{ "whenMatched", "fail" },
{ "whenNotMatched", "insert" }
})
};
await collection.AggregateAsync<BsonDocument>(pipeline);
the concat only can use string, not integer even so it has limitations.
Idis another concern which constructed based on the field. How about if in the future you need to add/remove new fields for the document, do you need to update those old documents with the new ID format? Why not consider the ObjectId/Guid that generated automatically during insertion?