Запросите объект C# Mongo, где свойство массива соответствует значению и возвращает объект без проекции

У меня есть такой объект:

public class DriverDocument : Document
{
    public string?          Email        { get; set; }
    public List<SubscriptionInfo> Subscriptions { get; set; } = new();
}

public class SubscriptionInfo
{
    public string           Id                  { get; set; } = null!;
    public string           PartnerId           { get; set; } = null!;
}

и я бы вернул все DriverDocument, где Subscriptions.PartnerId = '123'.

Если я использую .Find(Builders<DriverDocument>.Filter.ElemMatch(x => x.Subscriptions, s => s.PartnerId == partnerId)), это вернет все DriverDocument, где ЛЮБОЙ из Subscriptions содержит «123».

Я хочу вернуть DriverDocument И Subscriptions, которые содержат только соответствующие записи.

Если я сделаю что-то вроде приведенного ниже, мне придется использовать проекцию, что означает ручное сопоставление свойств. Если я добавлю новое свойство в DriverDocument, мне придется не забыть включить его в проекцию.

 .Find(Filter.ElemMatch(x => x.Subscriptions, y => y.PartnerId == partnerId))
 .Project(Projection.Expression(
            d => new DriverDocument
            {
                Id = d.Id,
                Status = d.Status,
                FirstName   = d.FirstName,
                LastName    = d.LastName,
                Email       = d.Email,
                
                Subscriptions = d.Subscriptions
                    .Where(sub => sub.PartnerId == partnerId)
                    .ToList()
            }));

Есть ли другой способ вернуть записи DriverDocument И Subscriptions только с теми данными, которые мне нужны, без необходимости сопоставления свойств и без BsonDocument?

используйте агрегацию mongoplayground.net/p/pEuf0Gm74F8. преобразован в С#

cmgchess 19.08.2024 21:21

все конвейеры и выражения в драйвере C# поддерживают неявное приведение из строки. См. здесь mongodb.com/docs/drivers/csharp/current/faq/… и stackoverflow.com/questions/73651325/… или stackoverflow.com/questions/77553043/…

dododo 19.08.2024 22:26

поэтому, хотя некоторые выражения, как в примерах @cmgchess, не полностью поддерживаются драйвером, вы всегда можете предоставить их в простой строковой форме.

dododo 19.08.2024 22:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вместо использования простого поиска с проекцией вы можете запустить конвейер агрегации как минимум с двумя этапами:

  • $match для фильтрации документов, имеющих подписку с соответствующим идентификатором партнера.
  • $set, чтобы назначить свойство Subscriptions, чтобы список содержал только элементы с соответствующим идентификатором партнера.

Для простоты вы можете использовать LINQ для настройки конвейера агрегации. Обратите внимание, что на момент написания этой статьи образец работал для драйвера MongoDB C# 2.28 и типа свойства List<SubscriptionInfo>. Альтернативное решение см. ниже.

var pipeline = new EmptyPipelineDefinition<DriverDocument>()
    .Match(x => x.Subscriptions.Any(y => y.PartnerId == "123"))
    .Set(x => new DriverDocument()
    {
        Subscriptions = x.Subscriptions.Where(y => y.PartnerId == "123").ToList()
    });
var result = await (await coll.AggregateAsync(pipeline)).ToListAsync();

Таким образом, вам не придется помнить о необходимости добавлять в оператор новые свойства.


Приложение. Образец генерируется в следующем конвейере агрегации MongoDB.
[
  {
    $match: {
      Subscriptions: {
        $elemMatch: { PartnerId: "123" }
      }
    }
  },
  {
    $set: {
      Subscriptions: {
        $filter: {
          input: "$Subscriptions",
          as: "y",
          cond: { $eq: ["$$y.PartnerId", "123"] }
        }
      }
    }
  }
]

Альтернативное решение без Linq

Если вы не можете выполнить обновление до версии 2.28 или тип вашего свойства отличается от List<SubscriptionInfo>, вы также можете настроить этап $set как BsonDocument, например:

var partnerId = "123";
var setStage = BsonDocument.Parse("""
{
  $set: {
    Subscriptions: {
      $filter: {
        input: "$Subscriptions",
        as: "y",
        cond: { $eq: ["$$y.PartnerId", "%%PARTNER_ID%%"] }
      }
    }
  }
}
""".Replace("%%PARTNER_ID%%", partnerId));
var pipeline = new EmptyPipelineDefinition<DriverDocument>()
    .Match(x => x.Subscriptions.Any(y => y.PartnerId == "123"))
    .AppendStage<DriverDocument, DriverDocument, DriverDocument>(setStage);
var result = await (await coll.AggregateAsync(pipeline)).ToListAsync();

это выглядит идеально и отлично работает в Compass. Однако я получаю эту неясную ошибку linq: ExpressionNotSupportedException: выражение не поддерживается: x.Subscriptions.Where(y => (y.PartnerId == "123")).ToList(), потому что сериализаторы членов и значений несовместимы. MongoDB.Driver.Linq.Linq3Implementation.Translators.Expressi‌​onToSetStageTranslat‌​ors.ExpressionToSetS‌​tageTranslator.Throw‌​IfMemberAndValueSeri‌​alizersAreNotCompati‌​ble (выражение выражения, IBsonSerializermemberSerializer, IBsonSerializer valueSerializer)

Jon 20.08.2024 14:01

В моем примере это работает с классами, указанными в вопросе. Если я изменю тип свойства Subscriptions в классе DriverInfo, например. до IList<SubscriptionInfo>, возникает исключение. Является ли свойство SubscriptionsList<SubscriptionInfo> в вашем коде?

Markus 20.08.2024 14:30

В версии драйвера 2.27 было внесено изменение, изменившее такое поведение. Я понизил версию с 2.28 до 2.26 и также получил ошибку List<SubscriptionInfo>. Какая у вас версия драйвера?

Markus 20.08.2024 14:38

Ага, у меня 2.25

Jon 20.08.2024 14:45

Я обновил ответ решением, которое не использует Linq для этапа $set; надеюсь, это поможет :-)

Markus 20.08.2024 15:00

Спасибо, попробую, обновление до 2.28 вызвало ошибки компиляции, которые не имеют смысла, поскольку мне предлагается ссылаться на 2.30, которой, похоже, не существует.

Jon 20.08.2024 15:13

Другие вопросы по теме