У меня есть такой объект:
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# поддерживают неявное приведение из строки. См. здесь mongodb.com/docs/drivers/csharp/current/faq/… и stackoverflow.com/questions/73651325/… или stackoverflow.com/questions/77553043/…
поэтому, хотя некоторые выражения, как в примерах @cmgchess, не полностью поддерживаются драйвером, вы всегда можете предоставить их в простой строковой форме.





Вместо использования простого поиска с проекцией вы можете запустить конвейер агрегации как минимум с двумя этапами:
$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();
Таким образом, вам не придется помнить о необходимости добавлять в оператор новые свойства.
[
{
$match: {
Subscriptions: {
$elemMatch: { PartnerId: "123" }
}
}
},
{
$set: {
Subscriptions: {
$filter: {
input: "$Subscriptions",
as: "y",
cond: { $eq: ["$$y.PartnerId", "123"] }
}
}
}
}
]
Если вы не можете выполнить обновление до версии 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.ExpressionToSetStageTranslators.ExpressionToSetStageTranslator.ThrowIfMemberAndValueSerializersAreNotCompatible (выражение выражения, IBsonSerializermemberSerializer, IBsonSerializer valueSerializer)
В моем примере это работает с классами, указанными в вопросе. Если я изменю тип свойства Subscriptions в классе DriverInfo, например. до IList<SubscriptionInfo>, возникает исключение. Является ли свойство SubscriptionsList<SubscriptionInfo> в вашем коде?
В версии драйвера 2.27 было внесено изменение, изменившее такое поведение. Я понизил версию с 2.28 до 2.26 и также получил ошибку List<SubscriptionInfo>. Какая у вас версия драйвера?
Ага, у меня 2.25
Я обновил ответ решением, которое не использует Linq для этапа $set; надеюсь, это поможет :-)
Спасибо, попробую, обновление до 2.28 вызвало ошибки компиляции, которые не имеют смысла, поскольку мне предлагается ссылаться на 2.30, которой, похоже, не существует.
используйте агрегацию mongoplayground.net/p/pEuf0Gm74F8. преобразован в С#