3

I have a MongoDB database with a few collections each of which stores objects of a specific type. I am trying to implement a generic selection function to operate on a specific collection depending on the type, like in the following definition:

object[] Select<T>(Func<T, bool> condition)

E.g., if one of the object types is a Person class, I would implement the following:

object[] Select<T>(Func<T, bool> condition)
{
   if (typeof(T) == typeof(Person))
   {
        Func<Person, bool> f = (Person p) => 
        {
            return true;
        };
        return this.collectionPersons.AsQueryable().Where(p=>f(p)).ToArray();
    }
    else // ...
}

This code compiles, but when I try to run it, I get a System.ArgumentException with the

Additional information: Unsupported filter:        
Invoke(value(System.Func`2[Person,System.Boolean]), {document}).

After perusing the API documentation I have the impression that it is generally not possible to use a lambda expression of abstract kind (like in the above example), but only those supported by FilterDefinitionBuilder such as Gt(), Eq() etc. I am curious if I understand this correctly or there does exist a possibility to query a collection with an abstract predicate (I am quite new to MongoDB C# driver).

2 Answers 2

2

Sure you could use Lambda Expressions as parameter. Driver will trabslate your expression to simple filters, it will be not possible to translate every Expression, but if it is possible, it will work. I don't really understand what are you doing in your sample, you don't use your condition

Your function should take not Func but Expression as argument:

public static T[] Select<T>(IMongoCollection<T> collection,  
                             Expression<Func<T, bool>> condition)
{
    if (typeof(T) == typeof(Person))
    {
         return collection.AsQueryable().Where(condition).ToArray();
    }
    return null;
}

And you could call it with expression:

var res = Select(this.collectionPersons, x => x.FirstName == "a3");

if you want to query just all items, as it looks like, you could do it as:

return this.collectionPersons.Find(p=>true).ToArray();

And you have to mention all namespaces in usings:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks Maksim, I tried to implement Expression instead of Predicate, exactly as you suggested. The following error is what I am getting: MongoDB.Driver.Linq.IMongoQueryable<Person>' does not contain a definition for 'Where' and the best extension method overload 'System.Linq.Queryable.Where<TSource>(System.Linq.IQueryable<TSource>, System.Linq.Expressions.Expression<System.Func<TSource,int,bool>>)' has some invalid arguments. I am naturally not going to query for all items in this way, 'return true;' was just the simplest case which did not work anyway.
I have tried it out now, with this method i have in answer (i have updated it a litle bit) i could query my collection.
@AlexKonnen perhaps you are missing some using statement?
Maksim, I don't think so: have checked: all of the mentioned are present...
Check your driver version. The definition of where is an extesion method in MongoDB.Driver.Linq (check if you have it in usings). And don't forget it takes Expression<Func<TSource, bool>> as a parameter, f in your question is not an expression, you should change type if you want to use it this way
|
0

My error was in how I called the predicate. Below is an example where it works:

public object[] SelectByPredicate<T>(Func<T, bool> predicate)
{
    if (typeof(T) == typeof(Person))
    {
        Func<Person, bool> f = (Person p) => 
        {
            T t = (T)Convert.ChangeType(p, typeof(T));
            return predicate(t);
        };

        return this.SelectPersonsByPredicate(f);
    }
    else if (typeof(T) == typeof(Pet))
    {
        Func<Pet, bool> f = (Pet p) => 
        {
            T t = (T)Convert.ChangeType(p, typeof(T));
            return predicate(t);
        };

        return this.SelectPetsByPredicate(f);
    }
    else
    {
        throw new NotSupportedException("Type not supported");
    }
}

private Person[] SelectPersonsByPredicate(Func<Person, bool> predicate)
{
    return this.collectionPersons.AsQueryable().Where(predicate).ToArray();
}

private Pet[] SelectPetsByPredicate(Func<Pet, bool> predicate)
{
    return this.collectionPets.AsQueryable().Where(predicate).ToArray();
}

Then, if you need a subset of persons by predicate, you just write something like

object[] personsSelected    = db.SelectByPredicate<Person>(p=>p.Name.Contains("Theo"));

It is not very elegant, I admit, in that it returns an object[] instead of T[], but for all practical purposes it is OK. If someone knows a better way, I would be interested.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.