Как использовать AggregateToCollection() для хранения результатов IMongoQueryable в коллекции

#c# #mongodb #linq #iqueryable

#c# #mongodb #linq #iqueryable

Вопрос:

Если я чего-то не упустил, в документации для драйвера C # MongoDB, похоже, есть большие пробелы.

Я пытаюсь взять IMongoQueryable (который является результатом различных операций LINQ Where, Select и т. Д.) И сохранить результаты в коллекции на стороне базы данных. Конечно, я мог бы выполнить итерацию на стороне клиента и сохранить ее таким образом, но даже в пакетах это неэффективно, а в оболочке это довольно простая операция для использования $merge или $out .

В коллекции есть метод AggregateToCollection<TResult>() , который, как я полагаю, является именно тем, что я хочу, но он принимает PiplineDefinition<TDocument, TResult> параметр, который я не очень представляю, как на самом деле генерировать из IMongoQueryable .

Я пробирался по кроличьей норе, используя GetExecutionModel() IMongoQueryable для получения BsonDocument строки or или Json, но тогда я все еще понятия не имею, как превратить это в PipelineDefinition то, что мне нужно!

Я бы подумал, что найду метод расширения IMongoQueryable , который позволил бы мне отправить его для объединения в коллекцию.

Пока что, чтобы иметь возможность отправлять IMongoQueryable в коллекцию, у меня есть что-то вроде:

     var executionModelDocument = queryable.GetExecutionModel().ToBsonDocument();

    // somehow turn the document into pipeline stages and a pipeline??

    await _database.GetCollection<TDocument>().AggregateToCollectionAsync<TResult>(pipeline);
 

Как мне на самом деле это сделать?

Ответ №1:

Хорошо, в конечном итоге это было проще, чем ожидалось, но все равно разочаровывало.

Прежде всего, оказывается, вы можете получить конвейер из IMongoQueryable as JSON, просто вызвав ToString() его. Хотя я говорю JSON, это не совсем так — вывод содержит полный JSON для конвейера в виде массива этапов, но имеет метку впереди и содержит JSON в круглых скобках. Я воспользовался ярлыком здесь и просто взял грязную подстроку:

 var queryableJson = queryable.ToString();
var trimmedDocument = queryableJson.Substring(10, queryableJson.Length - 11); // TODO: more reliably get the true json rather than blindly removing what should be "aggregate(" and ")"
 

Затем я повторно сериализовал JSON обратно в массив BsonDocument и сделал PipelineDefinition из него a (a BsonDocument[] может быть неявно преобразован в a PipelineDefinition ):

 PipelineDefinition<TDocument, TResult> pipelineQueryable = BsonSerializer.Deserialize<BsonDocument[]>(trimmedDocument);
 

Обратите внимание, что в то время как документация на http://mongodb.github.io/mongo-csharp-driver/2.4/reference/driver/definitions/#pipelines говорит, что я мог бы неявно использовать один BsonDocument элемент для конвейера, а не массив этапов, это неверно, если только в другом пространстве имен нет перегрузки, которую я не нашел.

Теперь, когда у нас есть определенный конвейер для IMongoQueryable, мы можем просто добавлять к нему этапы для достижения желаемого результата (в данном случае для объединения результатов конвейера в другую коллекцию). Вы можете указать свойства MergeStageOptions<TResult> объекта для управления поведением, но для меня значения по умолчанию работали нормально:

 var stageMerge = PipelineStageDefinitionBuilder.Merge<TResult, TResult>(_database.GetCollection<TResult>(), new MergeStageOptions<TResult>());
var mergePipeline = pipelineQueryable.AppendStage(stageMerge);
 

Используя наш недавно улучшенный конвейер, мы можем применить его к исходной коллекции, чтобы объединить выходные данные с целевой коллекцией:

 _database.GetCollection<TDocument>().AggregateToCollection(mergePipeline);
 

Для простоты то, что я продемонстрировал здесь, не является асинхронным, однако я воспользовался ожидаемыми методами в драйвере в моем реальном коде, поскольку синхронные методы просто переносят ожидаемые версии.

Это действительно все, что нужно было сделать! Когда у меня будет больше времени, я вернусь и попытаюсь пропустить этап сериализации-десериализации, поскольку он заметно медленный и не должен быть необходимым. Я также планирую превратить это в метод расширения для удобства.

Комментарии:

1. Я приму свой собственный ответ через несколько дней, если никому нечего сказать — я немного удивлен, что не смог найти никого, кто столкнулся с этой проблемой, что заставляет меня нервничать, что есть веская причина не делать этого, поэтому, если вы знаете какие-либо такие причины, сообщите нам всемздесь 🙂