reach4thelasers
reach4thelasers

Reputation: 26909

.NET MongoDB Bad Projection Specification

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

Answers (1)

mickl
mickl

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

Related Questions