修补REST API以部分更新.NET中的MongoDB

时间:2016-11-10 11:41:00

标签: c# mongodb rest updates partial

我有一个对象

Nov 10, 2016 4:46:33 PM org.apache.catalina.core.StandardContext listenerStart
SEVERE: Exception sending context initialized event to listener instance of class org.alfresco.repo.webdav.WebDAVSessionListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.eisenvault.autoMailgenerate.createUserHandler' defined in class path resource [alfresco/module/demoact1-repo/context/service-context.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.eisenvault.autoMailgenerate.CreateUserHandler]: Constructor threw exception; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1038)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:984)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:487)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:633)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:410)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
    at org.alfresco.web.app.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:63)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4939)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5434)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.eisenvault.autoMailgenerate.CreateUserHandler]: Constructor threw exception; nested exception is java.lang.NullPointerException
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:163)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1031)
    ... 23 more
Caused by: java.lang.NullPointerException
    at com.eisenvault.autoMailgenerate.CreateUserHandler.<init>(CreateUserHandler.java:30)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:148)
    ... 25 more

我想向我的API发送一个REST PATCH请求,只更新其中一个属性

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

将其解析为类

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

我现在需要更新MongoDB中的现有文档,但只更新属性public class SomeObject { public string A { get; set; } public string B { get; set; } public string C { get; set; } }

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

C

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

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

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

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

5 个答案:

答案 0 :(得分:5)

我建议您避免依赖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提供代码示例。

答案 1 :(得分:1)

您可以使用

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

但是,你应该小心。

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

如果您使用

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

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

答案 2 :(得分:0)

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

{ "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();

答案 3 :(得分:0)

在ASP.net core 3.1中,您可以使用JsonPatchDocument并在mongodb中进行替换

JsonPatch in ASP.NET Core web API

[HttpPatch]
public IActionResult JsonPatchWithModelState(
    [FromBody] JsonPatchDocument<Customer> patchDoc)
{
    if (patchDoc != null)
    {
        var customer = CreateCustomer();

        patchDoc.ApplyTo(customer, ModelState);

        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        return new ObjectResult(customer);
    }
    else
    {
        return BadRequest(ModelState);
    }
}

答案 4 :(得分:-1)

包含对象的数组将失败:

"array": [{"test": "value"}]将产生{array.test[0] : "value"},但是mongodb期望{array.test.0 : "value"}