#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 предоставит вам именно эту таблицу. Кажется, он хочет иметь возможность передавать таблицу, чтобы ее можно было вызывать для любой таблицы.