使用MongoDB C#驱动程序从父子层次结构中查找并更新节点

时间:2016-10-26 17:41:50

标签: c# mongodb mongodb-query mongodb-.net-driver

我有一个分级类别文档,如父级 - 儿童 - 儿童等......

{
    id: 1,
    value: {

    }Children: [{
        id: 2,
        value: {

        }Children: [{
            id: 3,
            value: {

            }Children: [{
                id: 4,
                value: {

                }Children: [{
                    id: 5,
                    value: {

                    }Children: [{
                        id: 6,
                        value: {

                        }Children: [{
                            id: 7,
                            value: {

                            }Children: []
                        }]
                    }]
                }]
            }]
        }]
    }]
}

在使用MongoDB C#驱动程序的此类文档中,如何找到Id = x

的节点

我试过这样的事情

var filter = Builders<Type>.Filter.Eq(x => x.Id, 3);
                var node = mongoDbRepository.GetOne(filter) ??
                             mongoDbRepository.GetOne(Builders<Type>.Filter.Where(x => x.Children.Any(c=>c.Id == 3)));

但这仅涵盖两个层面。在我的例子中,我有7个级别,我没有对级别深度的限制

找到该节点后,我需要更新该节点。

MongoDB Documentation讨论分层文档,但不包括我的场景。

3 个答案:

答案 0 :(得分:2)

在你的情况下,如果你

  

没有对等级深度的限制

你不能创建更新查询。您必须更改商店数据的架构:

https://docs.mongodb.com/v3.2/tutorial/model-tree-structures/

如果你的深度是固定的:

public class TestEntity
{
    public int Id { get; set; }
    public TestEntity[] Value { get; set; }
}

class Program
{
    static void Main()
    {
        const string connectionString = "mongodb://localhost:27017";
        var client = new MongoClient(connectionString);

        var db = client.GetDatabase("TestEntities");
        var collection = db.GetCollection<TestEntity>("Entities");

        collection.InsertOne(CreateTestEntity(1, CreateTestEntity(2, CreateTestEntity(3, CreateTestEntity(4)))));
        const int selctedId = 3;
        var update = Builders<TestEntity>.Update.AddToSet(x => x.Value, CreateTestEntity(9));

        var depth1 = Builders<TestEntity>.Filter.Eq(x => x.Id, selctedId);
        var depth2 = Builders<TestEntity>.Filter.Where(x => x.Value.Any(item => item.Id == selctedId));
        var depth3 = Builders<TestEntity>.Filter.Where(x => x.Value.Any(item => item.Value.Any(item2 => item2.Id == selctedId)));

        var filter = depth1 | depth2 | depth3;

        collection.UpdateMany(filter, update);

        // if you need update document on same depth that you match it in query (for example 3 as selctedId), 
        // you must make 2 query (bad approach, better way is change schema):
        //var testEntity = collection.FindSync(filter).First();
        //testEntity.Value[0].Value[0].Value = new[] {CreateTestEntity(9)}; //todo you must calculate depth what you need in C#
        //collection.ReplaceOne(filter, testEntity);
    }

    private static TestEntity CreateTestEntity(int id, params TestEntity[] values)
    {
        return new TestEntity { Id = id, Value = values };
    }
}

答案 1 :(得分:0)

您的示例文档中似乎有问题。如果父级有3个文件:_id,id和value,则示例应为

{
"_id" : ObjectId("581bce9064989cce81f2b0c1"),
"id" : 1,
"value" : {
    "Children" : [ 
        {
            "id" : 2,
            "value" : {
                "Children" : [ 
                    {
                        "id" : 3,
                        "value" : {
                            "Children" : [ 
                                {
                                    "id" : 4,
                                    "value" : {
                                        "Children" : [ 
                                            {
                                                "id" : 5,
                                                "value" : {
                                                    "Children" : [ 
                                                        {
                                                            "id" : 6,
                                                            "value" : {
                                                                "Children" : [ 
                                                                    {
                                                                        "id" : 7,
                                                                        "value" : {
                                                                            "Children" : []
                                                                        }
                                                                    }
                                                                ]
                                                            }
                                                        }
                                                    ]
                                                }
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}

} 请尝试以下函数,其中target是文档,x是您要更新的ID号。

    bool FindAndUpdate2(BsonDocument target, int x)
    {
        BsonValue id, children;
        while (true)
        {
            try
            {
                if (target.TryGetValue("_id", out children))
                {
                    id = target.GetValue(1);
                    children = target.GetValue(3);
                }
                else
                {
                    id = target.GetValue(0);
                    children = target.GetValue(2);
                }
                if (id.ToInt32() == x)
                {
                    Update(target);  //change as you like
                    return true;   //success
                }
                else
                    target = children[0] as BsonDocument;
            }
            catch (Exception ex)
            {
                return false;   //failed
            }
        }
    }

否则,如果父级有4个文件:_id,id,value和children,则示例应为

{
"_id" : ObjectId("581bdd3764989cce81f2b0c2"),
"id" : 1,
"value" : {},
"Children" : [ 
    {
        "id" : 2,
        "value" : {},
        "Children" : [ 
            {
                "id" : 3,
                "value" : {},
                "Children" : [ 
                    {
                        "id" : 4,
                        "value" : {},
                        "Children" : [ 
                            {
                                "id" : 5,
                                "value" : {},
                                "Children" : [ 
                                    {
                                        "id" : 6,
                                        "value" : {},
                                        "Children" : [ 
                                            {
                                                "id" : 7,
                                                "value" : {},
                                                "Children" : []
                                            }
                                        ]
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    }
]

{ "_id" : ObjectId("581bdd3764989cce81f2b0c2"), "id" : 1, "value" : {}, "Children" : [ { "id" : 2, "value" : {}, "Children" : [ { "id" : 3, "value" : {}, "Children" : [ { "id" : 4, "value" : {}, "Children" : [ { "id" : 5, "value" : {}, "Children" : [ { "id" : 6, "value" : {}, "Children" : [ { "id" : 7, "value" : {}, "Children" : [] } ] } ] } ] } ] } ] } ] 然后你可以试试这个:

}

答案 2 :(得分:0)

我有一个基于@DmitryZyr的答案的版本,并使用问题How do I create an expression tree calling IEnumerable<TSource>.Any(...)?的2个答案。感谢Aaron Heusser和Barry Kelly:

class Program
{
    #region Copied from Expression.Call question
    static MethodBase GetGenericMethod(Type type, string name, Type[] typeArgs, Type[] argTypes, BindingFlags flags)
    {
        int typeArity = typeArgs.Length;
        var methods = type.GetMethods()
            .Where(m => m.Name == name)
            .Where(m => m.GetGenericArguments().Length == typeArity)
            .Select(m => m.MakeGenericMethod(typeArgs));

        return Type.DefaultBinder.SelectMethod(flags, methods.ToArray(), argTypes, null);
    }

    static bool IsIEnumerable(Type type)
    {
        return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
    }

    static Type GetIEnumerableImpl(Type type)
    {
        // Get IEnumerable implementation. Either type is IEnumerable<T> for some T, 
        // or it implements IEnumerable<T> for some T. We need to find the interface.
        if (IsIEnumerable(type))
            return type;
        Type[] t = type.FindInterfaces((m, o) => IsIEnumerable(m), null);
        Debug.Assert(t.Length == 1);
        return t[0];
    }

    static Expression CallAny(Expression collection, Expression predicateExpression)
    {
        Type cType = GetIEnumerableImpl(collection.Type);
        collection = Expression.Convert(collection, cType); // (see "NOTE" below)

        Type elemType = cType.GetGenericArguments()[0];
        Type predType = typeof(Func<,>).MakeGenericType(elemType, typeof(bool));

        // Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)
        MethodInfo anyMethod = (MethodInfo)
            GetGenericMethod(typeof(Enumerable), "Any", new[] { elemType },
                new[] { cType, predType }, BindingFlags.Static);

        return Expression.Call(anyMethod, collection, predicateExpression);
    }
    #endregion

    public class TestEntity
    {
        public int Id { get; set; }
        public TestEntity[] Value { get; set; }
    }

    private static TestEntity CreateTestEntity(int id, params TestEntity[] values)
    {
        return new TestEntity { Id = id, Value = values };
    }

    static void Main(string[] args)
    {
        const string connectionString = "mongodb://localhost:27017";
        var client = new MongoClient(connectionString);

        var db = client.GetDatabase("TestEntities");
        IMongoCollection<TestEntity> collection = db.GetCollection<TestEntity>("Entities");

        collection.InsertOne(CreateTestEntity(1, CreateTestEntity(2, CreateTestEntity(3, CreateTestEntity(4)))));
        const int selectedId = 4;

        int searchDepth = 6;
        // builds the expression tree of expanding x => x.Value.Any(...)
        var filter = GetFilterForDepth(selectedId, searchDepth);
        var testEntity = collection.Find(filter).FirstOrDefault();
        if (testEntity != null)
        {
            UpdateItem(testEntity, selectedId);
            collection.ReplaceOne(filter, testEntity);
        }
    }

    private static bool UpdateItem(TestEntity testEntity, int selectedId)
    {
        if (testEntity.Id == selectedId)
        {
            return true;
        }
        if (UpdateItem(testEntity.Value[0], selectedId))
            testEntity.Value[0] = CreateTestEntity(11);
        return false;
    }

    private static FilterDefinition<TestEntity> GetFilterForDepth(int id, int depth)
    {
        // item
        var idEqualsParam = Expression.Parameter(typeof(TestEntity), "item");
        // .Id
        var idProp = Expression.Property(idEqualsParam, "Id");
        // item.Id == id
        var idEquals = Expression.Equal(idProp, Expression.Constant(id));
        // item => item.Id == id
        var idEqualsLambda = Expression.Lambda<Func<TestEntity, bool>>(idEquals, idEqualsParam);
        // x
        var anyParam = Expression.Parameter(typeof(TestEntity), "x");
        // .Value
        var valueProp = Expression.Property(anyParam, "Value");
        // Expression.Call would not find easily the appropriate .Any((TestEntity x) => x == id)
        // .Value.Any(item => item.Id == id)
        var callAny = CallAny(valueProp, idEqualsLambda);
        // x => x.Value.Any(item => item.Id == id)
        var firstAny = Expression.Lambda<Func<TestEntity, bool>>(callAny, anyParam);

        return NestedFilter(Builders<TestEntity>.Filter.Eq(x => x.Id, id), firstAny, depth);
    }

    static int paramIndex = 0;

    private static FilterDefinition<TestEntity> NestedFilter(FilterDefinition<TestEntity> actual, Expression<Func<TestEntity, bool>> whereExpression, int depth)
    {
        if (depth == 0)
        {
            return actual;
        }
        // paramX
        var param = Expression.Parameter(typeof(TestEntity), "param" + paramIndex++);
        // paramX.Value
        var valueProp = Expression.Property(param, "Value");
        // paramX => paramX.Value.Any(...)
        var callLambda = Expression.Lambda<Func<TestEntity, bool>>(CallAny(valueProp, whereExpression), param);
        return NestedFilter(Builders<TestEntity>.Filter.Where(whereExpression), callLambda, depth - 1) | actual;
    }
}

它仍然是固定长度深度搜索,但深度可以在动态上变化。只需一步即可升级代码以尝试第一级,第二级,......而且深度是无限的

相关问题