2017-02-03 14 views
2

У меня есть база данных MongoDB с несколькими коллекциями, каждая из которых хранит объекты определенного типа. Я пытаюсь реализовать функцию универсального выбора для работы на определенной коллекции в зависимости от типа, как в следующем определении:MongoDB с C#: поиск элементов с пользовательским предикатом

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

Например, если один из типов объектов является Person класс, я бы Реализовать следующее:

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 // ... 
} 

Этот код компилируется, но когда я пытаюсь запустить его, я получаю System.ArgumentException с

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

Ознакомившись в документации API у меня создается впечатление, что вообще невозможно использовать лямбда-выражение абстрактного вида (как в приведенном выше примере), но только те, которые поддерживаются FilterDefinitionBuilder, такие как Gt(), Eq() и т. д. Мне любопытно, если я понимаю это правильно или существует возможность запросить коллекцию с абстрактным предикатом (я совершенно новый для драйвера MongoDB C#).

ответ

1

Возможно, в качестве параметра вы можете использовать выражения лямбда. Драйвер будет трассировать ваше выражение для простых фильтров, это будет невозможно перевести каждое выражение, но если это возможно, оно будет работать. Я не очень понимаю, что вы делаете в вашей выборке, вы не используете состояние

Ваша функция должна принимать не Func, но выражение в качестве аргумента:

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; 
} 

И вы могли бы назвать его выражение:

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

, если вы хотите запросить только все элементы, как это выглядит, вы могли бы сделать это как:

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

И вы должны упомянуть всех пространств имен в usings:

using System; 
using System.Collections.Generic; 
using System.Linq.Expressions; 
using System.Linq; 
using MongoDB.Driver; 
using MongoDB.Driver.Linq; 
+0

Спасибо Maksim, я попытался реализовать Expression вместо Predicate точно так же, как вы предлагали. Следующая ошибка заключается в том, что я получаю: MongoDB.Driver.Linq.IMongoQueryable 'не содержит определения для «Где» и лучший метод перегрузки расширения. System.Linq.Queryable.Where (System.Linq.IQueryable , System.Linq.Expressions.Expression >) 'имеет некоторые недопустимые аргументы. Я, естественно, не буду запрашивать все элементы таким образом: 'return true;' был просто самым простым случаем, который не работал в любом случае. –

+0

Я пробовал это сейчас, с помощью этого метода у меня есть ответ (я обновил его бит litle), я мог запросить мою коллекцию. –

+0

@AlexKonnen, возможно, вам не хватает какой-либо инструкции? –

0

Моя ошибка была в том, как я назвал предикат. Ниже приведен пример, в котором он работает:

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(); 
} 

Затем, если необходимо подмножество лиц по предикату, вы просто написать что-то вроде

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

Это не очень элегантно, я признаю, в что он возвращает object[] вместо T[], но для всех практических целей это нормально. Если кто-то знает лучший способ, мне будет интересно.