Запросите объект 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?

🤔 А знаете ли вы, что...
C# также используется для разработки игр с помощью Unity3D, популярного игрового движка.


1
50
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();