MongoDB c # извлекает все совпадающие элементы в массиве в документе, используя построитель определений - PullRequest
1 голос
/ 20 мая 2019

У меня есть документ, который выглядит следующим образом по структуре с вложенным вложенным документом

{  
   "_id":ObjectId("50419077c2e6a1e18a489a0f"),
   "user":"Jone Doe",
   "fooArray":[  
      {  
         "plot":"circle",
         "color":"yellow",
      },
      {  
         "plot":"circle",
         "color":"red",
      },
      {  
         "plot":"square",
         "color":"green",
      }
   ]
}

И я хочу получить все подходящие элементы в fooArray в этом документе с круговым графиком.

Это то, что я пробовал

var filter = FilterBuilder.filter.Eq(doc => doc.User, User);
var projection = ProjectionBuilder
                .Exclude(doc => doc.Id)
                .Exclude(doc => doc.User)
                .Include(doc => doc.FooArray)
                .ElemMatch(x => x.FooArray, y => y.Plot == "circle");

var definition = new OperationDefinitions<ShapeDocument> { Filter = filter };
            return await Performer.Perform(definition, async (def, collection) =>
            {
                var findResult = collection.Find(def.Filter).Project(projection);

                var result = await findResult.SingleOrDefaultAsync();
            });

Это то, что я получаю

{  
   "fooArray":[  
      {  
         "plot":"circle",
         "color":"yellow",
      }
   ]
}

Но это дает мне только первый соответствующий элемент вместо всех элементов, у которых график соответствует кругу

{  
   "fooArray":[  
      {  
         "plot":"circle",
         "color":"yellow",
      },
      {  
         "plot":"circle",
         "color":"red",
      }
   ]
}

Я прочитал документацию mongodb, в которой упоминается

"Оператор $ elemMatch ограничивает содержимое поля из результатов запроса, чтобы оно содержало только первый элемент, соответствующий условию $ elemMatch."

Не совсем уверен, как этого добиться!

Ответы [ 3 ]

1 голос
/ 21 мая 2019

Вопрос не полностью описывает сценарий использования, поэтому я предложил несколько возможных вариантов для изучения на основе нескольких предположений, в частности, они зависят от доступности LINQ и нацеливания на один документ в время (и что вы, вероятно, не хотите больше кода, чем вам действительно нужно):

1) Вариант того, что у вас есть. Используйте стандарт find с проекцией и выражением LINQ.

var projection = Builders<ShapeDocument>.Projection
    .Expression(x => x.fooArray.Where(y => y.plot == "circle"));

var items1 = collection
    .Find(x => x.user == "Jone Doe")
    .Project(projection)
    .ToList();

2) Использовать конвейер агрегации (вы можете использовать ту же проекцию, что и выше)

var pipeline = collection
    .Aggregate()
    .Match(x => x.user == "Jone Doe")
    .Project(i => new
            {
                x = i.fooArray.Where(x => x.plot == "circle")
            });

var items2 = pipeline.SingleOrDefault();

3) Потяните документ обратно со всеми элементами массива, затем отфильтруйте локально, используя LINQ. С положительной стороны это небольшой объем читаемого кода, однако он возвращает весь документ перед фильтрацией. В зависимости от вашего точного использования это вполне может быть приемлемым.

var items3 = collection.AsQueryable()
    .SingleOrDefault(x => x.user == "Jone Doe")
    .fooArray.Where(x => x.plot == "circle");

Если LINQ действительно не вариант, то есть пример здесь , который показывает, как вы можете преобразовать проекцию в не нам LINQ. Полностью не проверено, но было бы что-то вроде:

var filter = new BsonDocument {
 {"input", "$items"},
 {"as", "item" },
 {"cond", new BsonDocument {
     // Fill in the condition values
     { "", new BsonArray { "", xxx } } }
   }
 };

var project = new BsonDocument {
 { "items", new BsonDocument { { "$filter", filter} } }
};

var pipeline = collection.Aggregate().Project(project);
0 голосов
/ 22 мая 2019

Я придумал отличный способ сделать это

var filter = FilterBuilder.filter.Eq(doc => doc.User, User);
var definition = new OperationDefinitions<ShapeDocument> { Filter = filter };
return await Performer.Perform(definition, async (def, collection) =>
{
var findResult = collection.Find(def.Filter).Project(doc => doc.fooArray.Where(x => x.Plot == "Circle"));
var result = await findResult.SingleOrDefaultAsync();
}
0 голосов
/ 21 мая 2019

вот приятное и простое решение, использующее MongoDB.Entities , которое является просто библиотекой-оболочкой для драйвера c #.

using MongoDB.Driver.Linq;
using MongoDB.Entities;
using System.Linq;

namespace StackOverflow
{
    public class User : Entity
    {
        public string Name { get; set; }
        public Foo[] Foos { get; set; }
    }

    public class Foo
    {
        public string Plot { get; set; }
        public string Color { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new DB("test");

            var user = new User
            {
                Name = "Jone Doe",
                Foos = new[]
                {
                    new Foo{ Plot = "circle", Color="yellow"},
                    new Foo{ Plot = "circle", Color="red"},
                    new Foo{ Plot = "square", Color="green"},
                }
            };

            user.Save();

            var circularFoos = DB.Collection<User>()
                                 .Where(u => u.Name == "Jone Doe")
                                 .SelectMany(u => u.Foos)
                                 .Where(f=>f.Plot=="circle").ToArray();
        }
    }
}

это совокупный запрос, который он генерирует:

  {
    "$match": {
      "Name": "Jone Doe"
    }
  },
  {
    "$unwind": "$Foos"
  },
  {
    "$project": {
      "Foos": "$Foos",
      "_id": 0
    }
  },
  {
    "$match": {
      "Foos.Plot": "circle"
    }
  }
...