我有一个分级类别文档,如父级 - 儿童 - 儿童等......
{
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讨论分层文档,但不包括我的场景。
答案 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;
}
}
它仍然是固定长度深度搜索,但深度可以在动态上变化。只需一步即可升级代码以尝试第一级,第二级,......而且深度是无限的