2014-01-22 5 views
5

Я пытаюсь выполнить поиск по ключевому слову объекта IQueryable, но есть ли способ сделать это без предварительного преобразования его в список?IQueryable with Foreach по результату

Мой код:

var customers = Customer.IQueryableAll(); // Method returns IQueryable containing all customers.  
var customerHolder = new List<Customer>(); 

foreach(var k in keywords) //keywords = List<string> 
{ 
    customerHolder.AddRange(customers.Where(x=>x.FirstName.Contains(k) || x.LastName.Contains(k) || x.CompanyName.Contains(k)).ToList()) 
} 

return customerHolder.Distinct(); 

Это работает, если я хочу, чтобы отобразить все результаты сразу, но проблема возникает, когда я хочу сделать подкачку. Функция все равно будет получать все записи из базы данных, до подкачки, поэтому ее очень неэффективно на больших таблицах. (т.е. customerHolder.Skip(5).Take(5);)

Есть ли способ интегрировать поисковую часть foreach в сам запрос?

ie.

customers.Where(x => x.Name.Contains(anythingInKeyWords)); 

EDIT: Для дальнейшего уточнения, я хочу поддерживать ИЛИ в выше, поэтому фильтрация и повторное фильтрование с несколькими, где пункты не будут работать. IE. Билл Джоб/Билл Гейтс> Поиск Билла Гейтса должен вернуть обе записи, потому что Билл Матчи.

+0

Единственный способ - динамически создавать фильтрацию выражения (в условиях ИЛИ). – Dennis

ответ

5

Вам нужно построить запрос, который ORs результат выражения вашего фильтра для каждого ключевого слова на сущность, что не очень практично, не используя динамический LINQ. Вот метод расширения, который будет делать только, что для вас:

public static class ExtensionMethods 
{ 
    public static IQueryable<TEntity> TestPerKey<TEntity, TKey>( 
     this IQueryable<TEntity> query, 
     IEnumerable<TKey> keys, 
     Expression<Func<TEntity, TKey, bool>> testExpression) 
    { 
     // create expression parameter 
     var arg = Expression.Parameter(typeof(TEntity), "entity"); 

     // expression body var 
     Expression expBody = null; 

     // for each key, invoke testExpression, logically OR results 
     foreach(var key in keys) 
     { 
      // constant expression for key 
      var keyExp = Expression.Constant(key); 

      // testExpression.Invoke expression 
      var invokeExp = Expression.Invoke(testExpression, arg, keyExp); 

      if(null == expBody) 
      { 
       // first expression 
       expBody = invokeExp; 
      } 
      else 
      { 
       // logically OR previous expression with new expression 
       expBody = Expression.OrElse(expBody, invokeExp); 
      } 
     } 

     // execute Where method w/ created filter expression 
     return query.Where((Expression<Func<TEntity, bool>>)Expression.Lambda(expBody, arg)); 
    } 
} 

Использование:

class TestEntity 
{ 
    public int Id { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string CompanyName { get; set; } 
} 

static void Main() 
{ 
    var testCollection = new TestEntity[]{ 
     new TestEntity(){ 
      Id = 0, 
      FirstName = "abc", 
      LastName = "def", 
      CompanyName = "ghi" 
     }, 
     new TestEntity(){ 
      Id = 1, 
      FirstName = "def", 
      LastName = "ghi", 
      CompanyName = "jkl" 
     }, 
     new TestEntity(){ 
      Id = 2, 
      FirstName = "ghi", 
      LastName = "jkl", 
      CompanyName = "mno" 
     }, 
     new TestEntity(){ 
      Id = 3, 
      FirstName = "bcd", 
      LastName = "efg", 
      CompanyName = "hij" 
     }, 
    }; 

    var keywords = new[]{ 
      "abc", 
      "jkl" 
     }; 

    var query = testCollection.AsQueryable() 
     .TestPerKey( 
      keywords, 
      (t, k) => 
       t.FirstName.Contains(k) || 
       t.LastName.Contains(k) || 
       t.CompanyName.Contains(k)); 

    foreach(var result in query) 
    { 
     Console.WriteLine(result.Id); 
    } 
} 

Update - попробуйте следующий метод расширения. Это более специфично, но должны работать с EF:

public static IQueryable<TestEntity> TestPerKey(
     this IQueryable<TestEntity> query, 
     IEnumerable<string> keys) 
    { 
     MethodInfo containsMethodInfo = typeof(string).GetMethod("Contains"); 

     // create expression parameter 
     var arg = Expression.Parameter(typeof(TestEntity), "entity"); 

     // expression body var 
     Expression expBody = null; 

     // for each key, invoke testExpression, logically OR results 
     foreach(var key in keys) 
     { 
      var expression = Expression.OrElse(
       Expression.OrElse(
        Expression.Call(Expression.Property(arg, "FirstName"), containsMethodInfo, Expression.Constant(key)), 
        Expression.Call(Expression.Property(arg, "LastName"), containsMethodInfo, Expression.Constant(key))) 
       , Expression.Call(Expression.Property(arg, "CompanyName"), containsMethodInfo, Expression.Constant(key))); 

      if(null == expBody) 
      { 
       // first expression 
       expBody = expression; 
      } 
      else 
      { 
       // logically OR previous expression with new expression 
       expBody = Expression.OrElse(expBody, expression); 
      } 
     } 

     // execute Where method w/ created filter expression 
     return query.Where((Expression<Func<TestEntity, bool>>)Expression.Lambda(expBody, arg)); 
    } 
+0

Приятно, но, к сожалению, это не сработает сразу с Entity Framework («Тип узла выражения LINQ« Invoke »не поддерживается в LINQ to Entities.»). Однако вы можете заставить его работать, вызвав «AsExpandable()» Linqkit в 'IQueryable', поступающем из EF. –

+0

ах, правда. Вам нужно будет сделать метод более конкретным и генерировать весь предикат динамически. – Moho

+0

добавил другой метод расширения, который должен работать с EF, но предикат которого жестко закодирован – Moho

-5

no Вы должны использовать ToList, чтобы получить желаемые результаты, поскольку LINQ - это полная терминология базового объекта коллекции.

+4

Это полностью ** не ** истинно ... – MarcinJuraszek

0

Вам необходимо создать полный IQueryable запрос и просто запустить его в конце. Попробуйте так:

keywords.All(k=>{customers = customers.Where(x=>x.FirstName.Contains(k) || x.LastName.Contains(k) || x.CompanyName.Contains(k)); return true;}); 

var result = customers.ToList();