MongoDB.live, free & fully virtual. Register Now MongoDB.live, free & fully virtual. Register Now

Using LINQ to pass IQueryable document into a constructor via Select

Just ran into an odd issue when trying to do something fairly simple with the Mongo C# driver (2.9.2) on .NET Core 3.1, so wanted to clarify whether it was a bug or known limitation of the Mongo-LINQ IQueryable implementation (or I’m doing something bad!)

We have a fairly simple collection containing documents with 4 fields, including a Bson ObjectId and a Guid. I am trying to expose this as a IQueryable, however in one of our API calls we process this with a .Where() clause and a .Select(x => new ApiClass(x)) where ApiClass has a single constructor which takes an instance of the (this doesn’t do much - converts the GUID to a string and hides some fields).

Evaluating the results of this query results in a crash when doing it in unit tests with the following error:
System.NotSupportedException
Could not find a member match for constructor parameter alert on type ApiClass in the expression tree new ApiClass({document}).
at MongoDB.Driver.Linq.Processors.ProjectionMapper.VisitNew(NewExpression node)

I half suspect it’s trying to pass in the document raw rather than as an instantiated , and can’t find the constructor for that but I’m guessing.

Some snippets of code:

// The Mongo entity
public class Alert 
{
    public Alert()
    {
    }
    
    public Alert(Guid userId, string publicationId)
    {
        UserId = userId;
        PublicationId = publicationId?.Trim();

        IsActive = true;
    }

    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }

    [BsonRequired]
    public Guid UserId { get; set; }

    [BsonRequired]
    public string PublicationId { get; set; }

    [BsonRequired]
    public bool IsActive { get; set; }
}

// Return structure for the API
public class ApiAlert
{
    public ApiAlert(CHub.Bff.Shared.MongoDb.Entities.Alert alert)
    {
        Id = alert.Id;
        UserId = alert.UserId.ToString();
        PublicationId = alert.PublicationId;
        IsActive = alert.IsActive;
    }

    public string Id { get; set; }

    public string UserId { get; set; }

    public string PublicationId { get; set; }

    public bool IsActive { get; set; }
}

// API call
    public IQueryable<ApiAlert> Alerts([Service] IResolverContext resolverContext, string? userId = "")
    {
        // alerts is IQueryable<Alert>
        var alerts = alertsService.GetAlerts();
        
        if (!string.IsNullOrEmpty(userId))
        {
            if (!Guid.TryParse(userId.Trim(), out var userGuid))
            {
                // some code to report the error

                return null;
            }

            alerts = alerts.Where(alert => alert.UserId == userGuid);
        }
        
        return alerts.Select(alert => new ApiAlert(alert));
}