0

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.

12
  • 1
    Your question contains too many questions, a high chance that may flag this question as closed. Is this possible without loading all of the objects into my application?, if you know which entry is missing by the criteria, you can construct the query to get those data needed. But your topic is quite unclear, without retrieving them in MongoDb, how will you construct the data to be inserted/patched. Commented Nov 4 at 8:21
  • 1
    The design of the Id is 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? Commented Nov 4 at 8:23
  • @YongShun I am not sure if there are multiple questions. I ask only one thing, how I can build that Id field. when I meant loading all data into my application, I meant =>loading them into application memory, iterate over them and insert into one by one. this is what I want to avoid Commented Nov 4 at 8:29
  • @YongShun adding new fields into id is not part of the question. as you said if there would such a need then I would ask a new question. current task is just what I asked Commented Nov 4 at 8:32
  • Okay, concluding your question, you are looking for how to insert/patch the data via an aggregation query or MongoDB .NET driver. Ya, it would be great to edit the question with only the main question. Thanks. Commented Nov 4 at 8:33

1 Answer 1

1

In your data model, the OwnerId field is of type Guid. MongoDB stores Guids/UUIDs as binary data of a specific subtype, sometimes even dependent on language or driver. There are no special methods to handle such a Guid-field, e.g. to format it the way that we know from C#/.NET. Hence it is impossible to solve this on the database level.

Of course, you can change the data type of the OwnerId to string, but this also has several downsides, the most important ones being that strings take up more storage space and are much harder to compare than a numeric or binary data type as the Guids might be formatted differently or have a different case.

As you need this specific form of id only to interconnect with another application, I'd propose that you build your primary data model with the optimal data types and store the "reference id" for the other application in a dedicated string field that is set up in C# code. This way you can build the field exactly as the other application needs it without compromises in your data model.

This might not be as efficient because you need to load the existing documents once for applying the change, but provides a better separation between the two applications. This is especially important as you do not have control over the other application. Maybe someone else has and changes the ids in their application; with this approach, you can apply a minor change to your data in this case without other parts of your application relying on the format that is not under your control.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.