Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic Linq Predicate throws "Unsupported Filter" error with C# MongoDB Driver

I have been trying to pass in a dynamic list of Expressions to a MongoDB C# Driver query using Linq ... This method works for me with regular Linq queries against an ORM, for example, but results in an error when applied to a MongoDB query ... (FYI: I am also using LinqKit's PredicateBuilder)

//
// I create a List of Expressions which I can then add individual predicates to on an 
// "as-needed" basis.
    var filters = new List<Expression<Func<Session, Boolean>>>();

//
// If the Region DropDownList returns a value then add an expression to match it.
// (the WebFormsService is a home built service for extracting data from the various 
// WebForms Server Controls... in case you're wondering how it fits in)
    if (!String.IsNullOrEmpty(WebFormsService.GetControlValueAsString(this.ddlRegion)))
    {
        String region = WebFormsService.GetControlValueAsString(this.ddlRegion).ToLower();
        filters.Add(e => e.Region.ToLower() == region);
    }

//
// If the StartDate has been specified then add an expression to match it.
    if (this.StartDate.HasValue)
    {
        Int64 startTicks = this.StartDate.Value.Ticks;
        filters.Add(e => e.StartTimestampTicks >= startTicks);
    }

//
// If the EndDate has been specified then add an expression to match it.
    if (this.EndDate.HasValue)
    {
        Int64 endTicks = this.EndDate.Value.Ticks;
        filters.Add(e => e.StartTimestampTicks <= endTicks);
    }

//
// Pass the Expression list to the method that executes the query
    var data = SessionMsgsDbSvc.GetSessionMsgs(filters);

The GetSessionMsgs() method is defined in a Data services class ...

public class SessionMsgsDbSvc
{

    public static List<LocationOwnerSessions> GetSessionMsgs(List<Expression<Func<Session, Boolean>>> values)
    {
        //
        // Using the LinqKit PredicateBuilder I simply add the provided expressions 
        // into a single "AND" expression ...
            var predicate = PredicateBuilder.True<Session>();
            foreach (var value in values)
            {
                predicate = predicate.And(value);
            }

        //
        // ... and apply it as I would to any Linq query, in the Where clause.
        // Additionally, using the Select clause I project the results into a 
        // pre-defined data transfer object (DTO) and only the DISTINCT DTOs are returned
            var query = ApplCoreMsgDbCtx.Sessions.AsQueryable()
                .Where(predicate)
                .Select(e => new LocationOwnerSessions 
                    { 
                        AssetNumber = e.AssetNumber, 
                        Owner = e.LocationOwner, 
                        Region = e.Region 
                    })
                .Distinct();

            var data = query.ToList();

            return data;
    }
}

Using the LinqKit PredicateBuilder I simply add the provided expressions into a single "AND" expression ... and apply it as I would to any Linq query, in the Where() clause. Additionally, using the Select() clause I project the results into a pre-defined data transfer object (DTO) and only the DISTINCT DTOs are returned.

This technique typically works when I an going against my Telerik ORM Context Entity collections ... but when I run this against the Mongo Document Collection I get the following error ...

Unsupported filter: Invoke(e => (e.Region.ToLower() == "central"), {document})

There is certainly something going on beneath the covers that I am unclear on. In the C# Driver for MongoDB documentation I found the following NOTE ...

"When projecting scalars, the driver will wrap the scalar into a document with a generated field name because MongoDB requires that output from an aggregation pipeline be documents"

But honestly I am not sure what that neccessarily means or if it's related to this problem or not. The appearence of "{document}" in the error suggests that it might be relevant though.

Any additional thoughts or insight would be greatly appreciated though. Been stuck on this for the better part of 2 days now ...

I did find this post but so far am not sure how the accepted solution is much different than what I have done.

like image 488
Gary O. Stenstrom Avatar asked May 05 '16 17:05

Gary O. Stenstrom


Video Answer


1 Answers

I'm coming back to revisit this after 4 years because while my original supposition did work it worked the wrong way which was it was pulling back all the records from Mongo and then filtering them in memory and to compound matters it was making a synchronous call into the database which is always a bad idea.

The magic happens in LinqKit's expand extension method

That flattens the invocation expression tree into something the Mongo driver can understand and thus act upon.

.Where(predicate.Expand())
like image 192
Buvy Avatar answered Nov 14 '22 21:11

Buvy