Подтвердить что ты не робот

LINQ to SQL - Как эффективно выполнять ИЛИ или OR для поиска нескольких критериев

У меня есть сайт ASP.NET MVC (который использует Linq To Sql для ORM) и ситуацию, когда клиент хочет средство поиска против базы данных на заказ, посредством которой они могут выбрать либо выполнить поиск "И" (все критерии совпадение) или поиск по "OR" (любое соответствие критериям). Запрос довольно сложный и длинный, и я хочу знать, есть ли простой способ заставить его сделать это без необходимости создавать и поддерживать две разные версии запроса.

Например, текущий поиск "И" выглядит примерно так (но это значительно упрощенная версия):

private IQueryable<SampleListDto> GetSampleSearchQuery(SamplesCriteria criteria)
{
   var results = from r in Table where
            (r.Id == criteria.SampleId) &&
            (r.Status.SampleStatusId == criteria.SampleStatusId) &&
            (r.Job.JobNumber.StartsWith(criteria.JobNumber)) &&
            (r.Description.Contains(criteria.Description))
        select r;

}

Я мог бы скопировать это и заменить && с || операторов, чтобы получить версию "OR", но чувствуйте, что должен быть лучший способ достичь этого. Есть ли у кого-нибудь предложения, как это можно достичь эффективным и гибким способом, который легко поддерживать? Спасибо.

4b9b3361

Ответ 1

Вы можете создать метод расширения по строкам

public static IQueryable<T> BoolWhere<T>(this IQueryable<T> source, Expression<Func<T, TValue>> selector, bool isOr) {
  //use isOr value to determine what expression to build and add to the source
}

где 'isOr' определит, использовать ли выражение 'и' или выражение 'или'. Затем вы можете построить свой запрос в строках

bool isOr = true; //or false
var results = Data.BoolWhere(r => r.Id == criteria.SampleId, isOr)
  .BoolWhere(r => r.Status.SampleStatusId == criteria.SampleStatusId, isOr)
  .BoolWhere(r => r.Job.JobNumber.StartsWith(criteria.JobNumber), isOr)
  .BoolWhere(r => r.Description.Contains(criteria.Description), isOr)

Ответ 2

Если у вас есть эти методы расширения:

public static class BoolExtensions
{
    public static bool And<TR, TC>(this IEnumerable<Func<TR, TC, bool>> statements, TR value, TC criteria)
    {
        foreach (var statement in statements)
        {
            if (!statement.Invoke(value, criteria))
            {
                return false;
            }
        }

        return true;
    }

    public static bool Or<TR, TC>(this IEnumerable<Func<TR, TC, bool>> statements, TR value, TC criteria)
    {
        foreach (var statement in statements)
        {
            if (statement.Invoke(value, criteria))
            {
                return true;
            }
        }

        return false;
    }
}

Затем вы можете объявить свои утверждения как список:

List<Func<TypeOfR, TypeOfC, bool>> statements = new List<Func<TypeOfR, TypeOfC, bool>>()
{
    { (r, c) => r.Id == c.SampleId },
    { (r, c) => r.Status.SampleStatusId == c.SampleStatusId },
    ...
};

И напишите свой запрос как:

var results = from r in Table where
        statements.And(r, criteria)
    select r;

или для версии ||:

var results = from r in Table where
        statements.Or(r, criteria)
    select r;

и просто поддерживать утверждения в одном месте.

Ответ 4

Возможно, проще, чем идея Дженса визуализировать, если вы ищете только комбинированный Or и комбинированный And (а не некоторый микс), вы всегда можете выразить свое равенство как список тестов, а затем применить операторов Any или All к нему. Например:

var queries = new List<Func<Table,SampleListDto,bool>>{
      ((a,b) => a.Id == b.SampleId),
      ((a,b) => a.Status.SampleStatusId == b.SampleStatusId),
      ((a,b) => a.Job.JobNumber.StartsWith(b.JobNumber)),
      ((a,b) => a.Description.Contains(b.Description))
};

var results = Table.Where(t=> queries.All(q => q(t, criteria)); // returns the && case
// or:  var results = Table.Where(t=>queries.Any(q=>q(t,criteria));

Как w/Jens ', не знаю, насколько эффективно это переводится в SQL, но если это становится проблемой, вы, вероятно, лучше конвертируете его в родной SQL.

Ответ 5

Вот информация о PredicateBuilder

Это должно быть совместимо с LINQ to SQL.

Может быть создана новая функция для использования функций PredicateBuilder И или Или:

private IQueryable<SampleListDto> GetSampleSearchQuery(
    SamplesCriteria criteria,
    Func<Expression<Func<SampleListDto, bool>>,
        Expression<Func<SampleListDto, bool>>,
        Expression<Func<SampleListDto, bool>>> logicExpr) 
{ 
   var results = from r in Table where 
            logicExpr(r => r.Id == criteria.SampleId,
            logicExpr(r => r.Status.SampleStatusId == criteria.SampleStatusId,
            logicExpr(r => r.Job.JobNumber.StartsWith(criteria.JobNumber),
            logicExpr(r => r.Description.Contains(criteria.Description)))))
        select r; 

}

Функции And и Or будут выглядеть так:

private IQueryable<SampleListDto> GetOrSampleSearchQuery(
    SamplesCriteria criteria) 
{ 
    return GetSampleSearchQuery(criteria, PredicateBuilder.Or<SampleListDto>);
}
private IQueryable<SampleListDto> GetAndSampleSearchQuery(
    SamplesCriteria criteria)
{
    return GetSampleSearchQuery(criteria, PredicateBuilder.And<SampleListDto>);
}

Ответ 6

Вы можете использовать шаблон T4 для генерации каждого метода. Таким образом, вы все равно будете иметь строго типизированные выражения. Щелкните правой кнопкой мыши свой проект и выберите add- > New Item- > Text Template

Шаблон будет выглядеть следующим образом:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>

namespace YourNamespaceName
{
    public partial class YourClassName
    {
    <# Generate(true); #>
    <# Generate(false); #>
    }
}
<#+
    private void Generate(bool isOr)
    {
        string op = isOr ? "||" : "&&";
        string methodName = "GetSampleSearchQuery" + (isOr ? "Or" : "And");
#>
    private IQueryable<SampleListDto> <#= methodName #>(SamplesCriteria criteria)
    {
        var results = from r in Table where
                (r.Id == criteria.SampleId) <#= op #>
                (r.Status.SampleStatusId == criteria.SampleStatusId) <#= op #>
                (r.Job.JobNumber.StartsWith(criteria.JobNumber)) <#= op #>
                (r.Description.Contains(criteria.Description))
            select r;
        return results;
    }
<#+
    }
#>

Просто измените свой класс на частичный и добавьте любые другие необходимые фильтры в шаблон T4 и используйте <#= op #> вместо оператора. (Щелкните правой кнопкой мыши шаблон T4 в проводнике решений и выберите "Запустить пользовательский инструмент", чтобы он был обновлен) В итоге вы получите два метода: GetSampleSearchQueryOr и GetSampleSearchQueryAnd для повторяющихся фильтров Or и And и поддерживаемых только в одном месте.

Ответ 7

Вы можете использовать технику, описанную в этом ответе. Метод Compose можно назвать так:   first.Compose(во-вторых, Expression.And); или   first.Compose(во-вторых, Expression.Or);

Таким образом, тип операции становится просто параметром.

Повторно применяя это ко всем вашим предложениям, вы можете построить требуемое выражение, и это все строго типизированный и чистый код выражения.