Всем привет.
переписываю тут проект на EF. Столкнулся с проблемой, которую никак не могу придумать как решить.
Есть запрос, в который в WHERE подставляется условие в зависимости от того, хотим ли мы сделать выборку по аккаунту, или по группе пользователей или по пользователю.
В примере который я приведу ниже, этот запрос обращается к таблице Table1, но таких таблиц около 30 и каждый раз запрос разный, ответ БД (структура) разные, но условие WHERE одно и то же.
Сейчас в программе в каждом контроллере идет вызов функции GetWhere, которая в зависимости от параметров формирует строку, которая подставляется в один из 30+ запросов (их значительно больше 30, думаю что не меньше 200).
И я не знаю как это переписать универсально, с тем чтобы осталось какое то подобие GetWhere, одинаково подходящее к всем переписанным запросам.
Если фильтровать после основного запроса структуру вида IQuerable то вылезают ошибки изза неизвестных типов. В общем я не особо профессионал, но как-то проблему надо решить, не хочется этот момент каждый раз делать руками...
SELECT a.StrParam1, a.StrParam2, SUM(a.IntParam1), SUM(a.IntParam2), MIN(a.ID),
MIN(a.Date1), MIN(a.StrParam3),
NULL, MIN(u.StrParam4), MIN(u.StrParam5), MIN(u.ID) FROM Table1 AS a
INNER JOIN Users AS u ON a.UserID=u.ID
WHERE " & WhereString1 & " AND (a.Date1 BETWEEN @datefrom AND @dateto) AND (u.Removed IS NULL)
GROUP BY a.StrParam1, a.StrParam2
ORDER BY SUM(a.IntParam1) DESC;
Вот тут WhereString1 мы заменяем на один из трех вариантов:
Если выборка по аккаунту:
u.AccountID=0
Если выборка по списку ID пользователей:
u.ID IN (1,2,3,4,5,6...)
Если выборка по списку групп:
u.GroupID IN (11,12,13,14,15...)
Здравствуйте, dmitry251, Вы писали:
D>Всем привет.
D>переписываю тут проект на EF. Столкнулся с проблемой, которую никак не могу придумать как решить.
D>Есть запрос, в который в WHERE подставляется условие в зависимости от того, хотим ли мы сделать выборку по аккаунту, или по группе пользователей или по пользователю.
D>В примере который я приведу ниже, этот запрос обращается к таблице Table1, но таких таблиц около 30 и каждый раз запрос разный, ответ БД (структура) разные, но условие WHERE одно и то же.
D>Сейчас в программе в каждом контроллере идет вызов функции GetWhere, которая в зависимости от параметров формирует строку, которая подставляется в один из 30+ запросов (их значительно больше 30, думаю что не меньше 200).
D>И я не знаю как это переписать универсально, с тем чтобы осталось какое то подобие GetWhere, одинаково подходящее к всем переписанным запросам.
В общем-то ничего лучше чем это видеть не приходилось:
Build Where Clause Dynamically in Linq.
Обычно в отдельном файле делается статический класс с набором методов WhereBy... или же один длинный метод с большим switch внутри.
Здравствуйте, BlackEric, Вы писали:
BE>Здравствуйте, dmitry251, Вы писали:
D>>Всем привет.
D>>переписываю тут проект на EF. Столкнулся с проблемой, которую никак не могу придумать как решить.
D>>Есть запрос, в который в WHERE подставляется условие в зависимости от того, хотим ли мы сделать выборку по аккаунту, или по группе пользователей или по пользователю.
D>>В примере который я приведу ниже, этот запрос обращается к таблице Table1, но таких таблиц около 30 и каждый раз запрос разный, ответ БД (структура) разные, но условие WHERE одно и то же.
D>>Сейчас в программе в каждом контроллере идет вызов функции GetWhere, которая в зависимости от параметров формирует строку, которая подставляется в один из 30+ запросов (их значительно больше 30, думаю что не меньше 200).
D>>И я не знаю как это переписать универсально, с тем чтобы осталось какое то подобие GetWhere, одинаково подходящее к всем переписанным запросам.
BE>В общем-то ничего лучше чем это видеть не приходилось: Build Where Clause Dynamically in Linq.
BE>Обычно в отдельном файле делается статический класс с набором методов WhereBy... или же один длинный метод с большим switch внутри.
Да, это неплохо, только классов у меня под сотню. Для каждого добавлять абсолютно одинаковый фильтр не комифильфо.
Здравствуйте, dmitry251, Вы писали:
D>Здравствуйте, BlackEric, Вы писали:
BE>>Здравствуйте, dmitry251, Вы писали:
D>>>Всем привет.
D>>>переписываю тут проект на EF. Столкнулся с проблемой, которую никак не могу придумать как решить.
D>>>Есть запрос, в который в WHERE подставляется условие в зависимости от того, хотим ли мы сделать выборку по аккаунту, или по группе пользователей или по пользователю.
D>>>В примере который я приведу ниже, этот запрос обращается к таблице Table1, но таких таблиц около 30 и каждый раз запрос разный, ответ БД (структура) разные, но условие WHERE одно и то же.
D>>>Сейчас в программе в каждом контроллере идет вызов функции GetWhere, которая в зависимости от параметров формирует строку, которая подставляется в один из 30+ запросов (их значительно больше 30, думаю что не меньше 200).
D>>>И я не знаю как это переписать универсально, с тем чтобы осталось какое то подобие GetWhere, одинаково подходящее к всем переписанным запросам.
BE>>В общем-то ничего лучше чем это видеть не приходилось: Build Where Clause Dynamically in Linq.
BE>>Обычно в отдельном файле делается статический класс с набором методов WhereBy... или же один длинный метод с большим switch внутри.
D>Да, это неплохо, только классов у меня под сотню. Для каждого добавлять абсолютно одинаковый фильтр не комифильфо.
А нельзя тут будет рефлексию задействовать, например пометить классы атрибутом и используя это генерить expression tree ?
D>Да, это неплохо, только классов у меня под сотню. Для каждого добавлять абсолютно одинаковый фильтр не комифильфо.
А в этих классах фильтруемые свойства одинаково называются (т.е. AccountId, GroupId, UserId) или везде по-разному?
Если одинаково, то можно вот так попробовать:
interface IFilterCriteria {
long? AccountId { get; }
long UIDList[] { get; }
long GroupIdList[] { get; }
}
public IQueryable<T> ApplyFilter<T>(this IQueryable<T> query, IFilterCriteria criteria) {
if (filter.AccountId.HasValue) {
Expression<Func<T,bool>> filterExp = BuildAccountIdExpression<T>(criteria.AccountId.Value); // TODO: построить filter expression в предположение, что у объекта T есть свойство AccountId
return query.Where(filterExp);
}
if (filter.UIDList.Count > 0) {
Expression<Func<T,bool>> filterExp = BuildUIDListExpression<T>(criteria.UIDList); // TODO: построить filter expression в предположение, что у объекта T есть свойство UserId
return query.Where(filterExp);
}
if (filter.GroupId.Count > 0) {
Expression<Func<T,bool>> filterExp = BuildGroupIdListExpression<T>(criteria.GroupIdList); // TODO: построить filter expression в предположение, что у объекта T есть свойство GroupId
return query.Where(filterExp);
}
return query;
}
Здравствуйте, RushDevion, Вы писали:
RD>А в этих классах фильтруемые свойства одинаково называются (т.е. AccountId, GroupId, UserId) или везде по-разному?
RD>Если одинаково, то можно вот так попробовать:
Если одинаково, то можно упростить и обойтись без expressions:
interface IFilterCriteria {
long? AccountId { get; }
long[] UIDList { get; }
long[] GroupIdList { get; }
}
interface IFilterable
{
long AccountId { get; }
long UserId { get; }
long GroupId { get; }
}
public IQueryable<T> ApplyFilter<T>(this IQueryable<T> query, IFilterCriteria criteria)
where T : IFilterable
{
if (filter.AccountId.HasValue) {
query = query.Where(o => o.AccountId == criteria.AccountId);
}
else if (filter.UIDList.Count > 0) {
query = query.Where(o => criteria.UIDList.Contains(o.UserId));
}
else if (filter.GroupIdList.Count > 0) {
query = query.Where(o => criteria.GroupIdList.Contains(o.GroupId));
}
return query;
}
Здравствуйте, dmitry251, Вы писали:
D>Да, это неплохо, только классов у меня под сотню. Для каждого добавлять абсолютно одинаковый фильтр не комифильфо.
То есть в каждом из этих классов есть UserID, верно?
Тогда можно попробовать сделать так:
1. Объявляем общий интерфейс и реализуем его во всех нужных классах:
public interface IUserBound
{
public string UserID {get; set;}
}
2. Делаем хелпер-класс:
public static class UserFilters
{
public static IQueryable<T> FilterByAccountId(this IQueryable<T> query, int accountId)
where T: IUserBound
=> from q in query
from u in db.Users
where q.UserID = u.ID && u.AccountID == accountId
select q;
public static IQueryable<T> FilterByUsers(this IQueryable<T> query, params int[] userIds)
where T: IUserBound
=> from q in query
where userIds.Contains(q.UserId)
select q;
public static IQueryable<T> FilterByUserGroups(this IQueryable<T> query, params int[] groupIds)
where T: IUserBound
=> from q in query
from u in db.Users
where q.UserID = u.ID && groupIds.Contains(u.GroupID)
select q;
}
3. В контроллерах, где нужно, вызываем соответствующие методы, заменяя
from db.SomeObjects на
from db.SomeObjects.FilterByUsers(userList) и так далее.
Если у вас там везде нужно делать одинаковое ветвление вида "если есть аккаунт ID — то по нему, иначе — по списку пользователей" и прочее, то можно всё это завернуть в 1 функцию, как предложил коллега
тут.