2

I know that this is not a feature and will not be implemented as stated here: https://jira.mongodb.org/browse/CSHARP-1750

However I really need to execute an aggregation with the $addFields operator. According to the comment on How to use Addfields in MongoDB C# Aggregation Pipeline

jira.mongodb.org/browse/CSHARP-1750. Read the resolution "won't fix". The reasoning is given, but it's clearly not on the roadmap at all. If you really want it then specify the pipeline manually with BsonDocument builders since that's all the API methods actually do anyway. And/Or vote up the JIRA issue, and with sufficient backing then maybe someone will think it worth considering for future work. – Neil Lunn Nov 3 '18

you can manually build the pipeline. How would I go about doing that, and can I use the aggregation I have before, and possibly after the manual string, so I don't have to build the entire thing by hand but only the addFields part?

I have tried

StringBuilder addFieldsDefinition = new StringBuilder();
addFieldsDefinition.AppendLine("{");
addFieldsDefinition.AppendLine("\"values"":{$reduce: {");
addFieldsDefinition.AppendLine("input: \"$values\",");
addFieldsDefinition.AppendLine("initialValue: {timeStamp: ISODate(\"0000-01-01T00:00:00.000+0000\")},");
addFieldsDefinition.AppendLine("in: {$cond: [{$and : [");
addFieldsDefinition.AppendLine("{$gte : [\"$$this.timeStamp\", \"$$value.timeStamp\"]},");
addFieldsDefinition.AppendLine("{$lte : [\"$$this.timeStamp\", ISODate(\"" + dt.ToString("yyyy-MM-dd") + "T" + dt.ToString("HH:mm:ss.fff") + "\")]}");
addFieldsDefinition.AppendLine("]}, \"$$this"", \"$$value\"]}");
addFieldsDefinition.AppendLine("}}");
addFieldsDefinition.AppendLine("}");

IAggregateFluent<BsonDocument> aggregate = col.Aggregate()
  .Match(filterDef)
  .Project(projectDef);
aggregate.Stages.Add("$addFields : " + addFieldsDefinition .ToString());

Wanting to get the Element in an Array where the 'timeStamp' field in the sub-document is the highest but under a specified dateTime. But the Code tosses me an Exception when I try to add the stage, saying a String can't be converted into an IPipelineStageDefinition.

I do not want to do the built in Aggregations like so (pseudocode)

.Unwind(values).Match(timestamp < dt).Sort(timeStamp).Limit(1)

because that's super slow.

Edit:

I now use the MongoDB.Bson Objects to create the stage: VB.NET Code (Sorry but I can't be bothered to convert that mess by hand)

Dim stage As New BsonDocument(New BsonElement("$addFields", New BsonDocument(New BsonElement("value",
    New BsonDocument(New BsonElement("$reduce", New BsonDocument(New List(Of BsonElement) From
        {
            New BsonElement("input", New BsonString("$" + FieldNames.VALUES_FIELDNAME)),
            New BsonElement("initialValue", New BsonDocument(New BsonElement("timeStamp", New BsonDateTime(DateTime.MaxValue)))),
            New BsonElement("in", New BsonDocument(New List(Of BsonElement) From
                {
                    New BsonElement("$cond", New BsonArray() From
                    {
                        New BsonDocument(New BsonElement("$and", New BsonArray() From
                        {
                            New BsonDocument(New BsonElement("$lte", New BsonArray() From {New BsonString("$$this.timeStamp"), New BsonString("$$value.timeStamp")})),
                            New BsonDocument(New BsonElement("$gte", New BsonArray() From {New BsonString("$$this.timeStamp"), New BsonDateTime(dt)}))
                        })),
                        New BsonString("$$this"),
                        New BsonString("$$value")
                    })
                }
            ))
        }
    ))))
)))
3
  • hi .. why don't you use Mongodb.Driver.Linq ? so you use the Linq syntax ? Commented Jan 22, 2019 at 10:18
  • @federicoscamuzzi: I suspect because the OP doesn't think it is possible using linq syntax. Perhaps add an answer demonstrating how if you know. Commented Jan 22, 2019 at 12:32
  • I'm not very comfortable with Linq. Even so, in VB.Net, it's even worse and less readable. Commented Jan 23, 2019 at 8:18

1 Answer 1

3

A BsonDocument can be converted into an IPipelineStageDefinition. To get the BsonDocument you want you just want:

var addFieldsDefinitionDoc = BsonDocument.Parse(addFieldsDefinition.ToString());
var stageElement = new BsonElement("$addFields", addFieldsDefinitionDoc);
var stage = new BsonDocument(stageElement)

Then to add it just use:

aggregate = aggregate.AppendStage(stage);

I'm not sure if aggregate.Stages.Add would do the same or not but I think AppendStage is probably the better way of doing it (though I've not found any documentation telling me what the right way of doing most things is so this was found through trial and error and inspecting the source to see how things work)...

You can even add additional stages using the normal stage builder functions like so:

aggregate = aggregate.Project(projectionDefinition);

or even

aggregate = aggregate
    .AppendStage(stage)
    .Project(projectionDefinition);
Sign up to request clarification or add additional context in comments.

5 Comments

Ah, that seems to work. Unfortunately the BsonDocument.Parse doesn't swallow the ISODate() string. But I can build the BsonDocument without the string builder by just adding elements as needed. Thank you for the answer.
Ah yes. I noticed the ISODate thing when I was testing. The problem seemed to be your initial date. If you change the year from 0000 to 0001 then it starts parsing correctly for me.
I managed to get it working by doing the whole thing with BsonDocument, BsonElements, BsonArrays and BsonStrings. However it doesn't seem to execute the aggregation stage I added.
Having just had a quick glance at the docs I suspect it might actually need aggregate = aggregate.AppendStage(stage); rather than just the line I used. Have you done that? I'm not at a place to look at my current code to confirm what I did in my working code right now but will try to remember to check in the morning and update the answer as appropriate.
Indeed that was the solution. Thank you.

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.