LINQ to SQL - Как эффективно выполнять поиск по ИЛИ или ИЛИ по нескольким критериям - PullRequest
10 голосов
/ 23 марта 2010

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

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

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;

}

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

Ответы [ 7 ]

2 голосов
/ 24 марта 2010

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

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;

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

2 голосов
/ 23 марта 2010

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

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)
1 голос
/ 12 февраля 2013

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

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

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

1 голос
/ 30 августа 2011

Вы можете использовать шаблон T4 для генерации каждого метода. Таким образом, вы все равно будете иметь строго типизированные выражения. Щелкните правой кнопкой мыши свой проект и выберите «Добавить» -> «Новый элемент» -> «Текстовый шаблон»

.

Шаблон будет выглядеть так:

<#@ 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 для фильтра «И» и «И», оба из которых поддерживаются в одном месте.

1 голос
/ 23 марта 2010

Вот информация о 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>);
}
1 голос
/ 23 марта 2010

Возможно, проще, чем идея Йенса, чтобы визуализировать, если вы ищете только комбинированное И или комбинированное И (а не какое-то смешивание), вы всегда можете выразить свое равенство в виде списка тестов, а затем применить операторы Любой или Все к этому. Например:

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

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

1 голос
/ 23 марта 2010
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...