Reputation: 26909
I'm in the process of upgrading a system from the Legacy Mongo Drivers to the new ones. I've got an issue with the following query.
var orgsUnitReadModels = _readModelService.Queryable<OrganisationalUnitReadModel>()
.Where(x => locations.Contains(x.Id))
.Select(x => new AuditLocationItemViewModel
{
Id = x.Id,
Name = x.Name,
AuditRecordId = auditRecordId,
Type = type,
IsArchived = !x.IsVisible,
AuditStatus = auditStatus
}).ToList();
It produces the following error message, which I don't understand. I would be grateful for assistance explaining what this means and how to fix it.
MongoDB.Driver.MongoCommandException: 'Command aggregate failed: Bad projection specification, cannot exclude fields other than '_id' in an inclusion projection: { Id: "$_id", Name: "$Name", AuditRecordId: BinData(3, 5797FCCCA90C8644B4CB84FED4236D4B), Type: 0, IsArchived: { $not: [ "$IsVisible" ] }, AuditStatus: 2, _id: 0 }.'
Upvotes: 1
Views: 1201
Reputation: 49975
In this example LINQ's Select statement gets translated into MongoDB's $project. Typically you use 0
(or false
) to exclude fields and 1
or true
to include fields in a final result set. Of course you can also use the dollar syntax to refer to existing fields which happens for instance for Name
.
The problem is that you're also trying to include some in-memory constant values as part of the projection. Unfortunately one of them (type
) is equal to 0
which is interpreted as if you would like to exclude a field called Type
from the pipeline result.
Due to this ambiguity MongoDB introduced $literal operator and you can try following syntax in Mongo shell:
db.col.aggregate([{ $project: { _id: 0, Id: 1, Name: 1, Type: { $literal: 0 } } }])
It will return 0
as a constant value as you expect. The MongoDB .NET driver documentation mentions literal here but it looks like it only works for strings.
There's a couple of ways you can solve your problem, I think the easier is to run simpler .Select
statement first and then use .ToList()
to make sure the query is materialized. Once it's done you can run another in-memory .Select()
to build your OrganisationalUnitReadModel
:
.Where(x => locations.Contains(x.Id))
.Select(x => new { x.Id, x.Name, x.IsVisible }).ToList()
.Select(x => new AuditLocationItemViewModel
{
Id = x.Id,
Name = x.Name,
Type = type,
IsArchived = !x.IsVisible,
AuditStatus = auditStatus
}).ToList();
Upvotes: 3