【问题标题】:Patch REST API to Partial Update MongoDB in .NET修补 REST API 以在 .NET 中部分更新 MongoDB
【发布时间】:2020-07-15 03:52:48
【问题描述】:

我有一个对象

{
  "_id": "testobject",
  "A": "First line",
  "B": "Second line",
  "C": "Third line"
}

我想向我的 API 发送一个 REST PATCH 请求以仅更新这些属性之一

{
  "_id": "testobject",
  "C": "Forth line"
}

这被解析成一个类

public class SomeObject {
  public string A { get; set; }
  public string B { get; set; }
  public string C { get; set; }
}

我现在需要更新 MongoDB 中的现有文档,但只更新属性 C

我可以为这条记录创建一个更新定义

UpdateDefinition<SomeObject> update = Builders<SomeObject>.Update.Set(x => x.C, <value of property C>)

或者我可以硬编码检查每个属性以查看它是否为空

IList<UpdateDefinition<SomeObject>> updates = new List<UpdateDefinition<SomeObject>>();
if (!string.IsNullOrEmpty(C)) {
  updates.Add(UpdateDefinition<SomeObject> update = Builders<SomeObject>.Update.Set(x => x.C, <value of property C>));
}
if (!string.IsNullOrEmpty(C)) {
  updates.Add(UpdateDefinition<SomeObject> update = Builders<SomeObject>.Update.Set(x => x.C, <value of property C>));
}

但是,如果我有许多属性和许多子属性,这可能会很快变得非常大。另一个问题是,如果我故意将属性的值设置为空,那么它根本不会更新记录,因为它正在寻找非空的字段。

如何在 .NET 中动态地对 MongoDB 文档进行部分更新,以便我有一个通用的 PATCH API 调用,它可以采用文档具有的任何参数并且只更新指定的属性?

【问题讨论】:

    标签: c# mongodb rest updates partial


    【解决方案1】:

    我建议您避免依赖 1.x 遗留 API,因为它在 2.x 中也得到了完美支持,如下面的示例代码所示。

    var client = new MongoClient();
    var database = client.GetDatabase("test");
    var collection = database.GetCollection<BsonDocument>("test");
    
    var changesJson = "{ a : 1, b : 2 }";
    var changesDocument = BsonDocument.Parse(changesJson);
    
    var filter = Builders<BsonDocument>.Filter.Eq("_id", 1);
    
    UpdateDefinition<BsonDocument> update = null;
    foreach (var change in changesDocument)
    {
        if (update == null)
        {
            var builder = Builders<BsonDocument>.Update;
            update = builder.Set(change.Name, change.Value);
        }
        else
        {
            update = update.Set(change.Name, change.Value);
        }
    }
    
    //following 3 lines are for debugging purposes only
    //var registry = BsonSerializer.SerializerRegistry;
    //var serializer = registry.GetSerializer<BsonDocument>();
    //var rendered = update.Render(serializer, registry).ToJson();
    
    //you can also use the simpler form below if you're OK with bypassing the UpdateDefinitionBuilder (and trust the JSON string to be fully correct)
    update = new BsonDocumentUpdateDefinition<BsonDocument>(new BsonDocument("$set", changesDocument));
    
    var result = collection.UpdateOne(filter, update);
    

    感谢 Robert Stam 提供代码示例。

    【讨论】:

      【解决方案2】:

      你可以使用

      IMongoUpdate updateDoc = new UpdateDocument("$set", doc);
      collection.Update(Query.EQ("_id",id), updateDoc);
      

      但是,你应该小心。

      如果您首先将文档反序列化为 SomeObject,则所有字段都将获得其默认值(字符串为 null,整数为 0 等)。如果您使用该对象进行更新,您的 json 字符串中不存在的字段将被更新为其默认值。

      如果你使用

      var bsonDoc = BsonSerializer.Deserialize<BsonDocument>(jsonString);  
      IMongoUpdate updateDoc = new UpdateDocument("$set", bsonDoc);
      collection.Update(Query.EQ("_id",id), updateDoc);
      

      您在数据库中的文档将仅针对您的 jsonString 中存在的字段进行更新

      【讨论】:

        【解决方案3】:

        不确定是否有人在这里 >= 20 年 6 月,但是我做了以下操作。我正在使用 NewtonSoft JObject/JArray,我想创建一个 mongo 更新解析器/函数,它不知道传入的模式并且也会构建嵌套文档。我必须习惯的另一件事(我是 Mongo 的新手)是 Bson 更新文档中键的语法,即

        { "key.full.path.into.nested.document" : "valueToSet" }
        

        因此,在尝试了几种手动/递归地解释传入 JSON 文档的嵌套/包含路径的方法后,我终于找到并且可以完美地使用 JToken.Path 属性。

        无论如何,希望这对某些人有用。这只是一个示例,并对文档结构做了一些假设,但在当前形式下非常有用。而且,像我一样,我认为它可能会帮助一些正在学习 Mongo 及其 C# 驱动程序的人,同时还使用 JSON.Net 来包装传入的 REST 请求。

            public BsonDocument ParseUpdateRequest(JObject req)
            {
                BsonDocument bson = new BsonDocument();
                Parse(req, ref bson);
        
                BsonDocument parsedBson = new BsonDocument();
                parsedBson["$set"] = bson;
                return parsedBson;            
            }
            private void Parse(JObject req, ref BsonDocument bson)
            {
                /**
                 * Could use a parent key/node in each recursion call or just use the JToken path
                 * string.IsNullOrEmpty(parentNode) ? field.Key : parentNode + "." + field.Key;
                 **/ 
                string key;
                JToken val;
                foreach (var field in req)
                {
                    key = field.Value.Path;                
                    val = field.Value;                
                    switch (val.Type)
                    {
                        case JTokenType.String:
                            bson.Add(key, (string)val);
                            break;
                        case JTokenType.Integer:
                            bson.Add(key, (int)val);
                            break;
                        case JTokenType.Float:
                            bson.Add(key, (float)val);
                            break;
                        case JTokenType.Date:
                            DateTime dt = (DateTime)val;
                            bson.Add(key, dt.ToUniversalTime());                        
                            break;
                        case JTokenType.Array:
                            BsonArray bsonArray = ParseArray((JArray)val);
                            bson.Add(key, bsonArray);
                            break;
                        case JTokenType.Object:
                            Parse((JObject)val, ref bson);
                            break;
                    }
                }
                return;
            }
        
            private BsonArray ParseArray(JArray source)
            {
                BsonArray bson = new BsonArray();            
                foreach (JToken field in source)
                {   
                    switch (field.Type)
                    {
                        case JTokenType.String:
                            bson.Add((string)field);
                            break;                    
                        case JTokenType.Date:
                            DateTime dt = (DateTime)field;
                            bson.Add(dt.ToUniversalTime());
                            break;
                        case JTokenType.Integer:
                            bson.Add((int)field);
                            break;
                        case JTokenType.Float:
                            bson.Add((float)field);
                            break;
                        case JTokenType.Object:
                            BsonDocument nestedDoc = new BsonDocument();
                            Parse((JObject)field, ref nestedDoc);
                            bson.Add(nestedDoc);
                            break;
                    }
                }
                return bson;
            }
        

        这是我写的一些简单的测试代码:

                    ModelUser user = new ModelUser();
                ControllerApp app = new ControllerApp();
                ControllerApp.Instance.User = user;
                JObject req = new JObject();
                req["first"] = "First";
                req["last"] = "Last";
                req["usertype"] = "parent";
                req["pw"] = "q345n3452345n2345";
                req["array"] = JArray.Parse("[ '1', '2', '3' ]");
                req["dateTest"] = DateTime.UtcNow;
                req["profile"] = new JObject();
                req["profile"]["name"] = new JObject();
                req["profile"]["name"]["first"] = "testUpdateFirst";
        
                BsonDocument bd;
                bd = user.ParseUpdateRequest(req);
                string s = bd.ToJson();
        

        【讨论】:

          【解决方案4】:

          包含对象的数组将失败:

          "array": [{"test": "value"}] 将导致 {array.test[0] : "value"},但 mongodb 期望 {array.test.0 : "value"}

          【讨论】:

          • 嘿@Luca,欢迎!这似乎与问题无关。但是,您可以查看其他贡献者的答案,其中包含有关如何用 C# 语言解决它的更多详细信息。另外,请阅读 StackOverflow 中的 How do I write a good answer? 部分,它将帮助您获得更多声誉。 =)
          猜你喜欢
          • 2020-09-07
          • 2023-01-30
          • 1970-01-01
          • 2021-07-27
          • 2014-10-27
          • 1970-01-01
          • 2019-07-13
          • 2019-04-28
          • 1970-01-01
          相关资源
          最近更新 更多