0

重複の可能性:
LINQ To SQL例外:ローカルシーケンスは、Contains演算子を除くクエリ演算子のLINQtoSQL実装では使用できません

私は次のクエリを試しています:

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演算子を除くクエリ演算子のLINQtoSQL実装では使用できません。」

誰かが私が間違っていることと、この検索を実行する正しい方法を教えてもらえますか?

簡単に言うと、ユーザーが「Hello World」のような文字列を入力できるようにし、helloまたはworld、あるいはその両方を検索するクエリを生成できるようにしようとしています。ただし、ユーザーは任意の数の単語を入力できます。

4

3 に答える 3

1

最も簡単なオプションは、おそらく手動でラムダ式を作成することです。

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")))
于 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関数を作成することにより、パフォーマンスを低下させたり、SQLCLR統合を使用したりする可能性のあるインタープリタークエリとローカルクエリを組み合わせる必要があります。

于 2013-01-31T18:36:04.377 に答える