[CSHARP-1998] UpdateDefinitionBuilder fails when updating enumerable field type with "object" Created: 12/Jun/17  Updated: 07/Feb/22  Resolved: 07/Feb/22

Status: Closed
Project: C# Driver
Component/s: Linq
Affects Version/s: 2.4.4
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Martin Lobger Assignee: Unassigned
Resolution: Done Votes: 2
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Windows


Attachments: Text File Program.cs    
Epic Link: CSHARP-3615

 Description   

When using UpdateDefinitionBuilder<> to update field parsing in an anonymous type as "object", it will fail to update the field correctly - introducing _t and _v.

This issue relates to CSHARP-1957

Attached is Program.cs that exposes this issue.

using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
 
namespace Mongo.Test
{
 
  public class Person
  {
    public string Name { get; set; }
    public char Gender { get; set; }
    public override string ToString() => $@"{GetType().Name}: ({Gender}) {Name}";
  }
 
 
  public class Group
  {
    public ObjectId Id { get; set; } = new ObjectId();
    public string Name { get; set; }
    public List<Person> Members { get; set; }
    public override string ToString() => $@"{GetType().Name}: {Id}, Name: {Name}, Members: [{string.Join(", ", Members)}]";
  }
 
 
  class Program
  {
    static void Main()
    {
      var mongoUrl = MongoUrl.Create(@"mongodb://localhost/test");
      var client = new MongoClient(mongoUrl);
      client.DropDatabase(mongoUrl.DatabaseName);
      var database = client.GetDatabase(mongoUrl.DatabaseName);
      var collection = database.GetCollection<Group>(nameof(Group));
 
      var group = new Group
      {
        Name = "Class A1",
        Members = new List<Person>
        {
          new Person { Name ="Lea", Gender = 'F' }
        }
      };
      collection.InsertOne(group);
      var result = collection.Find(g => g.Members.Any(p => p.Gender == 'F')).FirstOrDefault();
      Console.WriteLine(result); // #1: Document added perfectly
 
      group.Members.AddRange(new List<Person>
      {
        new Person { Name ="Linus", Gender = 'M' },
        new Person { Name ="Julius", Gender = 'M' }
      });
      UpdateMemebrs(collection, group.Id, group.Members); // #2: Document updated perfectly
 
      group.Members.Add(new Person { Name = "Martin", Gender = 'M' });
 
      // #3.1: Updating members using reflection to get property value...
      var propertyInfo = typeof(Group).GetProperty("Members");
      UpdateMemebrs(collection, group.Id, propertyInfo.GetValue(group)); // NOTE: the "Members" field is actually of type List<Person> but we are parsing <object>
      // #3.2: ...will "change" the document so find does not work
 
      Console.ReadLine();
    }
 
 
    private static void UpdateMemebrs<TProp>(IMongoCollection<Group> collection, ObjectId groupId, TProp members)
    {
      var filter = Builders<Group>.Filter.Eq(p => p.Id, groupId);
      var update = Builders<Group>.Update.Set(nameof(Group.Members), members);
      Console.WriteLine(update.Render(BsonSerializer.LookupSerializer<Group>(), BsonSerializer.SerializerRegistry));
      collection.UpdateOne(filter, update);
 
      // Now, test that the update was good
      var result = collection.Find(g => g.Members.Any(p => p.Gender == 'F')).FirstOrDefault();
      Console.WriteLine(result?.ToString() ?? "Not Found");
    }
  }
}
}



 Comments   
Comment by James Kovacs [ 07/Feb/22 ]

This works correctly on recent versions of the driver. Please open a new ticket with a complete repro if UpdateBuilder<T>.Set is not working as expected.

Comment by Svetoslav Milenov [ 30/Oct/17 ]

The issue is bigger than this or CSHARP-1957

It appears, that UpdateBuilder<T>.Set("someprop", someValue) stores the value as String, b/c it infers the type from the field provided, i.e. it does .Set<string>("someprop", value.ToString()). At least that what my tests show when using even simple filed of type IEnumerable<string>.

The same problem exists, if using a more generic property expression, like Expression<Func<T, object>> instead of a strictly typed expression Expression<Func<T, IEnumerable<string>>.

As far as FieldDefinition<T> is used to represent the field, I think that the proper behavior (as T is known) is to interrogate the class type, and use the property type instead of that's supplied in the Set method field generic type param.

My concrete use scenario is to have a list of Expression<Func<T, object>> to update any number of properties, without exposing mongo internals outside the repository. I.e. I have a generic repository like IRepository<T>, and update method Update(T resource, IEnumerable<Expression<Func<T,object>>> propsToUpdate).

Generated at Wed Feb 07 21:41:19 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.