0

可能重复:
LINQ To SQL 异常:本地序列不能在查询运算符的 LINQ to SQL 实现中使用,但 Contains 运算符

我正在尝试以下查询:

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”这样的字符串,并生成一个查询,该查询将查找 hello 或 world 或两者。但是,用户可以输入任意数量的单词。

4

3 回答 3

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

例如,如果searchArraycontains { "Hello", "World" },生成的 lambda 将是:

obj => (obj.Name.Contains("Hello") || obj.Name.Contains("World")) 
   || (obj.Genre.Contains("Hello") || obj.Genre.Contains("World")))
于 2013-01-31T19:13:26.357 回答
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 (...)
于 2013-01-31T18:25:39.150 回答
0

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

于 2013-01-31T18:36:04.377 回答