Сделайте метод поиска универсальным с помощью LINQ

#c# #linq #generics

#c# #linq #обобщения

Вопрос:

В моем проекте есть метод, который повторяется снова и снова:

 public PAC PAC_GetByCodiPac(string codiPac)

{

var sel = _gam.PAC.Where(pac => pac.CODI_PAC == codiPac);

            if (sel.Count() > 0)
                return sel.First();
            return null;
        }
  

Таблица PAC означает (пациент), поэтому у меня есть эти методы для всех имеющихся у меня таблиц.
Как я могу создать универсальный метод для этого?
Заранее спасибо.

Комментарии:

1. Можете ли вы сказать мне, что такое _gam?

2. Используете ли вы Entity Framework? Linq2SQL? Что-то еще?

3. Я думаю, что методы расширения Where () и FirstOrDefault () уже достаточно универсальны для ваших нужд

4. С помощью вызова Count вы выполняете итерацию по всему набору данных, даже если возвращаете только первое совпадение. Вместо этого вы должны использовать _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac); .

Ответ №1:

Вот ваш универсальный метод. Обратите внимание, что, как указывали другие, FirstOrDefault лучше, чем count, а затем first, поэтому я использую его здесь. Но также возможно написать выражение так, чтобы оно имитировало то, что делает ваш исходный код. Пожалуйста, дайте мне знать, если вам понадобится дополнительная помощь с этим.

 public static T GetByCodi<T>(IQueryable<T> table, string codi, string fieldName) where T : class
{
    // x
    ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
    Expression currentExpression = parameter;
    Type currentType = typeof(T);
    PropertyInfo property = currentType.GetProperty(fieldName);

    // x.CODI_xxx
    currentExpression = Expression.Property(currentExpression, property);

    // x.CODI_xxx == codi
    currentExpression = Expression.Equal(currentExpression, Expression.Constant(codi));

    // x => x.CODI_xxx == codi
    LambdaExpression lambdaExpression = Expression.Lambda(currentExpression, parameter);

    return table.FirstOrDefault((Func<T, bool>)lambdaExpression.Compile());
}
  

Вы используете его следующим образом:

 PAC xxx = GetByCodi<PAC>(_gam.PAC, codiPac, "CODI_PAC");
  

Редактировать 1:
Я изменил код в соответствии с комментарием, чтобы вы могли передавать произвольное имя поля ID.

Комментарии:

1. Этот метод отлично работает со всеми таблицами, идентификатор которых начинается с «CODI_», но в моей базе данных есть несколько таблиц с разными именами идентификаторов. Существует способ узнать идентификатор таблицы, и их методы выполняются автоматически независимо от названия идентификатора.

2. Я изменяю приведенный выше код так, чтобы он принимал имя поля id в качестве параметра, который вы можете передать. Если вы удовлетворены ответом, пожалуйста, примите его.

Ответ №2:

Я вижу, что то, что вы спросили, является очень простым where запросом, даже не требующим наличия его в отдельном методе. Также вы можете просто улучшить ссылку на запрос следующим образом:

 public PAC PAC_GetByCodiPac(string codiPac)
{
   return _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
}
  

FirstOrDefault вернет первый элемент в массиве, в противном случае он вернет null.

Комментарии:

1. даже если бы я не стал выделять это в отдельные методы для каждой таблицы, FirstOrDefault это правильный путь. Встроенный FirstOrDefault !

2. точно, но иногда нам приходится создавать простые методы в BL, которые могут содержать только одну строку, но это бизнес-метод

Ответ №3:

Если вам нужен универсальный метод, который позволяет указывать любую таблицу и любой предикат для записей из этой таблицы, то вы действительно не сможете получить ничего лучше встроенных Where<T>(...) и (как уже указывали другие) FirstOrDefault<T>(...) методов расширения.

Тогда ваш код выглядел бы примерно так:

 var result = _gam.PAC.Where(pac => pac.CODI_PAC == codiPac).FirstOrDefault();
// OR
var result = _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
  

Лучшее, что вы могли бы получить тогда, написав свой собственный универсальный метод, было бы следующим:

 public T FirstOrDefault<T>(IQueryable<T> source,
    Expression<Func<T, bool>> predicate)
{
    return source.Where(predicate).FirstOrDefault();
    // OR
    // return source.FirstOrDefault(predicate);
}
  

И это действительно просто избыточно. Особенно когда ваш вызывающий код был бы на самом деле длиннее, используя вспомогательный метод:

 var result = FirstOrDefault(_gam.PAC, pac => pac.CODI_PAC == codiPac);
// versus
var result = _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
  

И что еще хуже, ваш код больше не использует плавный, составной синтаксис. Это просто затрудняет читаемость и обслуживание.

Если вы придерживаетесь использования IQueryable<T> методов расширения, то вы можете создать композицию следующим образом:

 var result = _gam.PAC
        .Where(pac => pac.CODI_PAC == codiPac)
        .Where(pac => pac.SomeOtherProperty == someOtherValue)
        .FirstOrDefault();
// OR

var result = (from pac in _gam.PAC
              where pac.CODI_PAC == codiPac
              where pac.SomeOtherProperty == someOtherValue
              select pac).FirstOrDefault();
  

Здесь следует отметить одну очень важную вещь: predicate параметр в IQueryable<T>.Where<T>(...) методе расширения имеет тип Expression<Func<T, bool>> . Это позволяет IQueryable<T> поставщику создавать собственный SQL (или другой собственный запрос поставщика) в самый последний момент, прежде чем возвращать результат.

Неиспользование Expression<Func<T, bool>> означает, что ваш запрос будет эквивалентен этому:

 var result =
    _gam.PAC
        .ToArray()
        .Where(pac => pac.CODI_PAC == codiPac)
        .FirstOrDefault();
  

И это будет означать, что запрос загрузит каждую запись из таблицы «PAC» в память, прежде чем выбрать первый отфильтрованный результат и выдать остальные результаты.

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

Я надеюсь, что это поможет.

Комментарии:

1. Привет, спасибо за ответ, как вы думаете, что было бы лучше, описанный выше метод, написанный zespri (который является универсальным) или имеющий в моем BL множество методов (GetByCodi с использованием FirstOrDefult), таких как таблицы в моей БД? Что было бы лучше подумать о производительности и скорости.

2. @ramo2712 — Не в обиду зеспри — он написал какой-то очень продвинутый код, который выдает менее выразительную функцию, чем встроенный Where метод. Я не знаю, зачем вам нужно иметь много методов в вашем BL. Ваш BL должен либо ограничивать вас только теми методами, к которым вы должны получить доступ — что означает, что вам не нужно много методов, — либо он должен предоставлять выразительный способ доступа к вашим данным, чтобы стандартный LINQ был там хорош. Чего вы пытаетесь достичь?

3. У меня есть решение для веб-сайта проекта и библиотеки доступа к данным, в которой у меня есть методы GetByCodi. Поэтому не пишите код LINQ на страницах aspx, просто вызывайте мои библиотечные методы, и библиотека значительно растет (база данных большая), поэтому я пытаюсь создавать универсальные методы. Большое вам спасибо за ваш ответ. Я создаю такие методы, как print, как универсальный, но при этом я бы использовал этот var result = _gam.PAC.FirstOrDefault (pac => pac.CODI_PAC == codiPac);

Ответ №4:

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

Комментарии:

1. Он хочет сделать это не только для table PAC, но и для всех других таблиц в его базе данных.

2. Я не уверен в его проблеме, но он говорит, что этот метод «повторяется снова и снова», я предполагаю, что это одно и то же.

Ответ №5:

Простым решением будет:

 //a generic method
private PAC PAC_GetPAC(Func<PAC, bool> predicate)
{
   return _gam.PAC.Where(predicate).FirstOrDefault();
}

public PAC PAC_GetPACById(long id)
{
   return PAC_GetPAC(p => p.ID == id);
}

public PAC PAC_GetByCodiPac(string codiPac)
{
   return PAC_GetPAC(p => pac.CODI_PAC == codiPac);
}
  

Комментарии:

1. опять же, я думаю, что это будет работать только для table PAC, потому что _gam. PAC предоставит вам именно эту таблицу. Кажется, он хочет иметь возможность передавать таблицу, чтобы ее можно было вызывать для любой таблицы.