У меня есть коллекция, в которой хранится информация о сиренах – уведомлениях. Мне нужно получить сводную статистику пользователей по (длинному) идентификатору пользователя:
Структура документа Siren (обязательные поля только для вопросов):
{
"_id": ObjectId( "65fc94593273fc2ab6ff8960"),
"ownerid": NumberLong( "99999999"),
"listener": [
NumberLong( "11111111"),
NumberLong( "00000000")
],
"responsible": [
NumberLong( "11111111")
]
}
После двух дней борьбы я сделал рабочий запрос через JS.
db.sirens.aggregate([
{
"$match": {
"$or": [
{ "ownerid": userId },
{ "listener": userId },
{ "responsible": userId }
]}},
{
"$group": {
"_id": null,
"owner": { "$sum": { "$cond": [{ "$eq": ["$ownerid", userId ] }, 1, 0] }},
"responsible": { "$sum": { "$cond": [{ "$and": [
{ "$ne": ["$responsible", null] },
{ "$isArray": "$responsible" },
{ "$in": [userId, "$responsible"] }
] }, 1,0 ] }}
,"listener": { "$sum": { "$cond": [{ "$and": [
{ "$ne": ["$listener", null] },
{ "$isArray": "$listener" },
{ "$in": [userId, "$listener"] }
] }, 1,0 ] }}
}
}
]);
Но вот проблема, которую я не могу решить. поля «слушатель» и «ответственный» могут отсутствовать. А в JS это условие:{ "$isArray": "$responsible" },
обрабатывает ситуацию. Но я не могу сделать то же самое на C#.
Вот код, который у меня есть:
var query = sirens.AsQueryable<SirenRepresentation>()
.Where(_sirena => _sirena.OwnerId == userId
|| (_sirena.Listener != null && _sirena.Listener.Any(x => x == userId))
|| (_sirena.Responsible != null && _sirena.Responsible.Any(x => x == userId)))
.GroupBy(s => true)
.Select(g => new UserStatistics
{
SirenasCount = g.Sum(_siren => _siren.OwnerId == userId ? 1 : 0),
Subscriptions = g.Sum(_siren => (_siren.Listener != null && _siren.Listener.Contains(userId)) ? 1 : 0),
Responsible = g.Sum(_siren => (_siren.Responsible != null && _siren.Responsible.Contains(userId)) ? 1 : 0)
});
public class SirenRepresentation
{
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("ownerid"), BsonRepresentation(BsonType.Int64)]
public long OwnerId { get; set; }
[BsonRepresentation(BsonType.Int64)]
[BsonElement("listener")]
public long[] Listener { get; set; } = [];
[BsonRepresentation(BsonType.Int64)]
[BsonElement("responsible")]
public long[] Responsible { get; set; } = [];
[BsonElement("requests")]
//...
}
public class UserStatistics{
public int SirenasCount{get;set;}
public int Subscriptions{get;set;}
public int Responsible{get;set;}
}
Когда одно из полей отсутствует, перехватывается исключение:
Произошло исключение: CLR/MongoDB.Driver.MongoCommandException. исключение типа «MongoDB.Driver.MongoCommandException» произошло в System.Private.CoreLib.dll, но не обрабатывался в пользовательском коде: 'Команда Ошибка агрегирования: ошибка PlanExecutor во время агрегации:: вызвана :: $in требует массив в качестве второго аргумента, найдено: отсутствует.'
Обновлять. Фиксированный метод:
public async Task<UserStatistics> Get(long userId)
{
var query = sirens.AsQueryable()
.Where(_sirena => _sirena.OwnerId == userId
|| _sirena.Listener.Any(x => x == userId)
|| _sirena.Responsible.Any(x => x == userId))
.GroupBy(keySelector: x => true,
resultSelector: (_, _sirens) => new UserStatistics
{
SirenasCount = _sirens.Sum(x => x.OwnerId == userId ? 1 : 0),
Subscriptions = _sirens.Sum(_sirena => (_sirena.Listener ?? new long[] { }).Contains(userId) ? 1 : 0),
Responsible = _sirens.Sum(_sirena => (_sirena.Responsible ?? new long[] { }).Contains(userId) ? 1 : 0)
})
.FirstOrDefaultAsync();
return await query;
}
Большое спасибо Ён Шуну <3
P.S. MongoDB.Driver 2.24 также обеспечивает поведение для перевода some_array is Array
, но, к сожалению, в нем есть ошибки. Это переводится is Array
как выражение:
"$cond": {
"if": {
"$and": [{
"$or": [{
"$eq": ["$some_array._t", "Array"]
}, {
"$and": [{
"$isArray": "$some_array._t"
}, {
"$in": ["Array", "$some_array._t"]
}
...
А в моем случае для MongoDB v.7.0.6 это работает некорректно. Но если мы изменим "$eq": ["$some_array._t", "Array"]
-> {"$isArray": "$some_array"}
, то всё заработает.
Поскольку ваши поля Listener
и Responsible
, возможно, отсутствуют, вы можете указать для поля значение по умолчанию с помощью:
(_siren.Listener ?? new long[] { })
Таким образом, при преобразовании в запрос MongoDB это эквивалентно:
{ "$ifNull" : ["$responsible", []] }
Полный запрос:
var query = sirens.AsQueryable<SirenRepresentation>()
.Where(_siren => _siren.OwnerId == userId
|| (_siren.Listener != null
&& (_siren.Listener ?? new long[] { }).Any(x => x == userId))
|| (_siren.Responsible != null
&& (_siren.Responsible ?? new long[] { }).Any(x => x == userId)))
.GroupBy(s => true)
.Select(g => new UserStatistics
{
SirenasCount = g.Sum(_siren => _siren.OwnerId == userId ? 1 : 0),
Subscriptions = g.Sum(_siren => (_siren.Listener != null
&& (_siren.Listener ?? new long[] { }).Contains(userId)) ? 1 : 0),
Responsible = g.Sum(_siren => (_siren.Responsible != null
&& (_siren.Responsible ?? new long[] { }).Contains(userId)) ? 1 : 0)
});
Какую версию драйвера MongoDB .NET вы используете? Если вы используете LINQ Provider v3 и сможете достичь: MongoClientSettings settings = MongoClientSettings.FromConnectionString(mongoUrl); settings.LinqProvider = MongoDB.Driver.Linq.LinqProvider.V3;
Я использую: MongoDB.Driver - 2.24.0. А MongoClient.Client.Settings.LinqProvider* по умолчанию имеет значение V3.
В противном случае я бы предложил вам передать весь запрос как Экспортировать запрос на определенный язык без необходимости преобразования в запрос LINQ.
Да, я тестировал версию 2.24.0 и смог получить результат без ошибок. Было бы здорово, если бы вы рассказали, какую ошибку вы получили. Спасибо.
Да. Ты был прав. Когда я посмотрел на результат, я неправильно его интерпретировал. Спасибо. Оно работает. Тем не менее, в «is Array» есть ошибка. Полагаю, мне нужно опубликовать отчет об ошибке в MongoDB JIRA.
Я отправил отчет об ошибке команде MongoDB. И член их команды очень помог мне разобраться в преобразовании конструкций языка C# в строку запроса json. Особая благодарность Борису Догадову.
Короче говоря:
is T
для целей десериализации некоторого документа в некоторый объект типа T. В этом случае поле документа _t
должно быть установлено. Это описано в этой статье. Но это неподходящий способ проверить, является ли поле массивом.В результате мы можем проверить недостающее поле следующим образом:
Responsible = _sirens.Sum(_sirena => (Mql.Exists(_sirena.Responsible)
&& _sirena.Responsible.Contains(userId)) ? 1 : 0)
или
sirens.AsQueryable().Where(x=> Mql.Exists(x.Responsible));
Помните, что методы Mql применимы только для запросов LINQ. Также есть подробный ответ другому пользователю о тонкостях обработки LINQ
Это не работает таким образом. Потому что это не LINQ Select, а MongoDb.Driver.Linq Select. Этот метод преобразует условия из синтаксиса C# в инструкции json «под капотом» MongoDB. Так что это не может быть переведено
(_siren.Listener ?? new long[] { })
. Там есть перевод_siren.Listener is Array
, но, к сожалению, есть ошибка.