0

i have the following document structure:

{
   "name":"Test Online",
   "strategies":[
      {
         "strategyType":"Online",
         "strategyBudget":0,
         "extraElements":{
            "URL":"http:\\www.google.com"
         }
      },
      {
         "strategyType":"TV",
         "strategyBudget":0,
         "extraElements":{
            "ChannelSlots":[
               {
                  "channelName":"SIC",
                  "fromHour":"13:30:00",
                  "toHour":"13:30:00"
               },
               {
                  "channelName":"TVI",
                  "fromHour":"15:30:00",
                  "toHour":"16:30:00"
               }
            ]
         }
      },
      {
        "strategyType":"Outdoor",
        "strategyBudget":2000,
        "extraElements":{
            "Latitude": 8.123456,
            "Longitude": -16.123456

        }
      }

   ],
   "campaignBudget":3000
}

I want to create a function that gather the campaign Budget, divide it equally by all defined strategies (in this case 3) and then updates for each strategy the strategyBudget Field.

I have created all necessary objects so that i can do this in a typesafe way. For example i have for the strategy:

    [BsonDiscriminator("StrategyType", RootClass = true, Required = true)]
    [BsonKnownTypes(
        typeof(OnlineStrategy), 
        typeof(TvStrategy), 
        typeof(OutdoorStrategy))]
    public class Strategy
    {

        [JsonConverter(typeof(StringEnumConverter))]  // JSON.Net
        [BsonRepresentation(BsonType.String)]  // Mongo
        public  StrategyType StrategyType { get; set; }

        [JsonProperty("strategyBudget")]
        public  int StrategyBudget { get; set; }

        [JsonProperty("extraElements")]
        public ExtraElements ExtraElements { get; set; }
    }
}

so i can call Strategy.StrategyBudget and i get or set the budget.

My function so far is:

/**
         * Divide the budget of a campaign equally between all defined strategies for that campaign
         * so if a campaign has 3000 of budget and 3 strategies, 
         * each strategy would get 1000, and the campaign budget is set to 0
         */
        private void DivideBudgetEqually(string campaignID)
        {
            Campaign campaignRecord = _campaigns.Find(cpg => cpg.Id == campaignID).FirstOrDefault();
            int campaignBudget = campaignRecord.CampaignBudget;
            int numStrategies = campaignRecord.Strategies.Count;

            int equalBudget = campaignBudget / numStrategies;

            var campaignFilter = Builders<Campaign>.Filter.Eq(cpg => cpg.Id, campaignID);

            var strategyUpdate = Builders<Campaign>.Update.Set(cpg => cpg.Strategies[-1].StrategyBudget, equalBudget);
            var campaignUpdate = Builders<Campaign>.Update.Set(cpg => cpg.CampaignBudget, 0);

            // update the each strategy budget to equal value
            var resultStrategy = _campaigns.UpdateMany(campaignFilter, strategyUpdate);

            // update the campaign budget to 0, since we've distributed all the budget to the strategies
            var resultCampaign = _campaigns.UpdateMany(campaignFilter, campaignUpdate);


        }

But when i call the endpoint to execute this, i get the following:

web_1                  | fail: Microsoft.AspNetCore.Server.Kestrel[13]
web_1                  |       Connection id "0HLVNUBP8QGRB", Request id "0HLVNUBP8QGRB:00000001": An unhandled exception was thrown by the application.
web_1                  | MongoDB.Driver.MongoWriteException: A write operation resulted in an error.
web_1                  |   The positional operator did not find the match needed from the query.
web_1                  |  ---> MongoDB.Driver.MongoBulkWriteException`1[semasio_challenge_2.Models.Campaign]: A bulk write operation resulted in one or more errors.
web_1                  |   The positional operator did not find the match needed from the query.
web_1                  |    at MongoDB.Driver.MongoCollectionImpl`1.BulkWrite(IClientSessionHandle session, IEnumerable`1 requests, BulkWriteOptions options, CancellationToken cancellationToken)
web_1                  |    at MongoDB.Driver.MongoCollectionImpl`1.<>c__DisplayClass23_0.<BulkWrite>b__0(IClientSessionHandle session)
web_1                  |    at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSession[TResult](Func`2 func, CancellationToken cancellationToken)
web_1                  |    at MongoDB.Driver.MongoCollectionImpl`1.BulkWrite(IEnumerable`1 requests, BulkWriteOptions options, CancellationToken cancellationToken)
web_1                  |    at MongoDB.Driver.MongoCollectionBase`1.<>c__DisplayClass92_0.<UpdateMany>b__0(IEnumerable`1 requests, BulkWriteOptions bulkWriteOptions)
web_1                  |    at MongoDB.Driver.MongoCollectionBase`1.UpdateMany(FilterDefinition`1 filter, UpdateDefinition`1 update, UpdateOptions options, Func`3 bulkWrite)
web_1                  |    --- End of inner exception stack trace ---
web_1                  |    at MongoDB.Driver.MongoCollectionBase`1.UpdateMany(FilterDefinition`1 filter, UpdateDefinition`1 update, UpdateOptions options, Func`3 bulkWrite)
web_1                  |    at MongoDB.Driver.MongoCollectionBase`1.UpdateMany(FilterDefinition`1 filter, UpdateDefinition`1 update, UpdateOptions options, CancellationToken cancellationToken)
web_1                  |    at semasio_challenge_2.Services.CampaignService.DivideBudgetEqually(String campaignID) in /src/Services/CampaignService.cs:line 109

If i change the update function to any Async version, this error doesn't happen, but startegyBudget is not updated, but campaign one is.

What i'm doing wrong here?

2 Answers 2

1

Your campaignFilter needs to match one of the documents in the array in which you want to update with the positional operator $.

This:

var campaignFilter = Builders<Campaign>.Filter.Eq(cpg => cpg.Id, campaignID);

Needs to change to something like:

var campaignFilter = Builders<Campaign>.Filter.Eq(cpg => cpg.Id, campaignID)
                 & Builders<Campaign>.Filter.ElemMatch(cpg => cpg.Strategies, x => x.FieldToMatch == "test");
Sign up to request clarification or add additional context in comments.

4 Comments

This worked like a charm: var campaignFilter = Builders<Campaign>.Filter.Eq(cpg => cpg.Id, campaignID) & Builders<Campaign>.Filter.ElemMatch<Strategy>(cpg => cpg.Strategies, x => x.StrategyBudget != equalBudget);
Well, it's only updating the first strategy. I'm using UpdateManyAsync.
The positional operator will only update the first matched element in the array, the UpdateMany is for updating multiple documents, not array items.
If you want to do something more complex with updating arrays in documents look at array filters - docs.mongodb.com/manual/reference/operator/update/…
0

For reference, and using the Array Filters suggestion from Kevin Smith this is the code that executed that operation (since the usage of Array Filter in C# has few examples):

            var campaignFilter = Builders<Campaign>.Filter.Eq(cpg => cpg.Id, campaignID);
            // mongo c# driver has yet to have a fluent syntax for Array Filters.
            var updateDefinition = Builders<Campaign>.Update.Set("Strategies.$[stra].StrategyBudget", equalBudget);

            var arrayFilterStratergy = new List<ArrayFilterDefinition>();
            arrayFilterStratergy.Add(
                 new BsonDocumentArrayFilterDefinition<BsonDocument>(new BsonDocument("stra.StrategyBudget", new BsonDocument("$gte", 0)))
            );

            // update the each strategy budget to equal value
            var resultStrategy = await _campaigns.UpdateOneAsync(
                campaignFilter,
                updateDefinition,
                new UpdateOptions { ArrayFilters = arrayFilterStratergy });

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.