当搜索词的数量可以变化时,使用linq执行基于文本的搜索

时间:2013-01-31 18:17:21

标签: c# .net linq-to-sql

  

可能重复:
  LINQ To SQL exception: Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains operator

我正在尝试以下查询:

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是一个包含我要搜索的单个单词的数组,我将用户输入的字符串拆分并将结果放入此数组中。每当我尝试运行它时,我会收到以下错误: “除了Contains运算符之外,本地序列不能用于查询运算符的LINQ to SQL实现。”

任何人都可以告诉我我做错了什么以及执行此搜索的正确方法是什么?

简而言之,我正在尝试允许用户输入类似“Hello World”的字符串以及要生成的查询,以查找hello或world或两者。但是,用户可以输入任意数量的单词。

3 个答案:

答案 0 :(得分: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" },则生成的lambda将为:

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

答案 1 :(得分: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 (...)

答案 2 :(得分:0)

在您的情况下,您必须通过在数据库上创建CLR函数来组合可能损害性能或使用SQL CLR集成的解释和本地查询。