2013-01-31 50 views
0

Possible Duplicate:
LINQ To SQL exception: Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains operator使用LINQ來執行基於文本的搜索時,搜索詞數量可以是可變

我想下面的查詢:

var data = (from bk in DataContext.Book 
      where ((searchArray.Count() == 0 || searchArray.ToList().Any(x => bk.Name.Contains(x))) || 
         (searchArray.Count() == 0 || searchArray.ToList().Any(x => bk.Genre.Contains(x))))) 

其中searchArray是包含我想個別單詞的數組搜索,我分割用戶輸入的字符串,並把結果放在這個數組中。無論何時我嘗試運行此操作,我都會收到以下錯誤: 「除包含運算符外,本地序列不能用於查詢運算符的LINQ to SQL實現。」

誰能告訴我我在做什麼錯的,什麼是執行此搜索正確的方法是什麼?

概括地說,我想允許用戶輸入一個字符串,如「Hello World」的和將要生成一個查詢,將尋找任何招呼,或者世界或兩者兼而有之。但是,用戶可以輸入任意數量的單詞。

+0

_bk.Name.Contains_? _bk.Name_的類型是_IEnumerable _? –

回答

1

最簡單的方法可能是用手工打造的lambda表達式:

static class ContainsAny 
{ 
    private static readonly MethodInfo StringContains 
     = typeof(string).GetMethod("Contains", new[] { typeof(string) }); 

    public static Builder<T> Words<T>(IEnumerable<string> words) 
    { 
     return new Builder<T>(words); 
    }  

    public static Builder<T> Words<T>(params string[] words) 
    { 
     return new Builder<T>(words); 
    }  

    public sealed class Builder<T> 
    { 
     private static readonly ParameterExpression Parameter 
      = Expression.Parameter(typeof(T), "obj"); 

     private readonly List<Expression> _properties = new List<Expression>(); 
     private readonly List<ConstantExpression> _words; 

     internal Builder(IEnumerable<string> words) 
     { 
      _words = words 
       .Where(word => !string.IsNullOrEmpty(word)) 
       .Select(word => Expression.Constant(word)) 
       .ToList(); 
     } 

     public Builder<T> WithProperty(Expression<Func<T, string>> property) 
     { 
      if (_words.Count != 0) 
      { 
       _properties.Add(ReplacementVisitor.Transform(
        property, property.Parameters[0], Parameter)); 
      } 

      return this; 
     } 

     private Expression BuildProperty(Expression prop) 
     { 
      return _words 
       .Select(w => (Expression)Expression.Call(prop, StringContains, w)) 
       .Aggregate(Expression.OrElse); 
     } 

     public Expression<Func<T, bool>> Build() 
     { 
      if (_words.Count == 0) return (T obj) => true; 

      var body = _properties 
       .Select(BuildProperty) 
       .Aggregate(Expression.OrElse); 

      return Expression.Lambda<Func<T, bool>>(body, Parameter); 
     } 
    } 

    private sealed class ReplacementVisitor : ExpressionVisitor 
    { 
     private ICollection<ParameterExpression> Parameters { get; set; } 
     private Expression Find { get; set; } 
     private Expression Replace { get; set; } 

     public static Expression Transform(
      LambdaExpression source, 
      Expression find, 
      Expression replace) 
     { 
      var visitor = new ReplacementVisitor 
      { 
       Parameters = source.Parameters, 
       Find = find, 
       Replace = replace, 
      }; 

      return visitor.Visit(source.Body); 
     } 

     private Expression ReplaceNode(Expression node) 
     { 
      return (node == Find) ? Replace : node; 
     } 

     protected override Expression VisitConstant(ConstantExpression node) 
     { 
      return ReplaceNode(node); 
     } 

     protected override Expression VisitBinary(BinaryExpression node) 
     { 
      var result = ReplaceNode(node); 
      if (result == node) result = base.VisitBinary(node); 
      return result; 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      if (Parameters.Contains(node)) return ReplaceNode(node); 
      return Parameters.FirstOrDefault(p => p.Name == node.Name) ?? node; 
     } 
    } 
} 

有了這個代碼的地方,您可以撥打:

Expression<Func<Book, bool>> filter = ContainsAny 
    .Words<Book>(searchArray) 
    .WithProperty(book => book.Name) 
    .WithProperty(book => book.Genre) 
    .Build(); 

var data = DataContext.Book.Where(filter); 

例如,如果searchArray包含{ "Hello", "World" },生成的拉姆達將爲:

obj => (obj.Name.Contains("Hello") || obj.Name.Contains("World")) 
    || (obj.Genre.Contains("Hello") || obj.Genre.Contains("World"))) 
+0

根據上面提到的linq查詢,假設我想添加一個新的搜索BookTypeID == bookTypeId,這將來自用戶在其他搜索條件中選擇的下拉列表。我將如何將這添加到哪裏標準? – user1790300

+0

@ user1790300:既然你想匹配兩個過濾器,你可以簡單地調用'Where'多次:'Data.Book.Where(關鍵詞過濾)。凡我要找(書=> book.BookTypeID == bookTypeId)' –

+0

ExpressionVisitor,它在哪個庫中? – user1790300

0

如果我明白你正確地做什麼,你應該能夠凝聚查詢到:

from bk in DataContext.Book 
where searchArray.Contains(bk.Name) || searchArray.Contains(bk.Genre) 
select bk 

這基本上等同於SQL:

select bk.* 
from Book bk 
where bk.Name in (...) or bk.Genre in (...) 
+0

您已經錯過了'searchArray.Count()== 0'條件。 –

+0

如果Name包含字符串「The Great Gatsby」並且searchArray包含了字符串{「The」,「Great」,「Gatsby」},那麼這樣做會有效嗎? – user1790300

+0

@ user1790300:不,它只會執行精確的文本匹配。我錯過了你也在尋找部分比賽。 – goric

0

在你的情況你必須結合解釋和本地查詢,這可能會損害性能或通過在數據庫上創建CLR函數來使用SQL CLR集成。

相關問題