причина вашей проблемы
Вы должны знать между IEnumerable и IQueryable. В объекте IEnumerable есть все для перечисления по всем элементам: вы можете запросить первый элемент последовательности, и, получив элемент, вы можете запросить следующий элемент, пока не останется больше элементов.
IQueryable кажется похожим, однако IQueryable не содержит всего, чтобы перечислить последовательность. Он содержит Expression
и Provider
. Expression
является общей формой того, что должно быть запрошено. Provider
знает, кто должен выполнить запрос (обычно это система управления базами данных), как общаться с этим исполнителем и какой язык использовать (обычно что-то похожее на SQL).
Как только вы начнете перечислять, либо явно, вызывая GetEnumerator
и MoveNext
, либо неявно, вызывая foreach
, ToList
, FirstOrDefault
, Count
и т. Д., Отправляется Expression
Provider
, который переведет его на SQL и вызовет СУБД. Возвращенные данные представлены в виде объекта IEnumerable, который перечисляется с использованием GetEnumerator
Поскольку провайдер должен переводить Expression
в SQL, Expression
может вызывать только те функции, которые можно преобразовать в SQL. Увы, Provider
не знает ни HasProductsWithState
, ни ваших собственных определенных функций, и поэтому не может перевести его в SQL. Фактически, провайдер платформы сущностей также не знает, как переводить несколько стандартных функций LINQ, и поэтому они не могут использоваться AsQueryable
. См. Поддерживаемые и неподдерживаемые методы LINQ .
Так что вам придется придерживаться функций, которые возвращают IQueryable, где выражение содержит только поддерживаемые функции.
Описание класса
Увы, вы забыли дать нам свои классы сущностей, поэтому мне придется сделать некоторые предположения о них.
Очевидно, у DbContext есть как минимум три DbSet: MainGroups
, SubGroups
и Products
.
Кажется, существует отношение один ко многим (или, возможно, ко многим) между MaingGroups
и SubGroups
: каждый MainGroup
имеет ноль или более SubGroups
.
Кажется, что между SubGroups
и Products
также существует отношение один ко многим: каждый SubGroup
имеет ноль или более Products
.
Увы, вы забыли упомянуть, что возвращаемое отношение: каждый Product
принадлежит ровно одному SubGroup
(один ко многим), или каждый Product
принадлежит нулю или более SubGroups
(много-к -many`)
Если вы следовали первым соглашениям кода структуры сущностей, у вас будут классы, подобные этим:
class MainGroup
{
public int Id {get; set;}
...
// every MainGroup has zero or more SubGroups (one-to-many or many-to-many)
public virtual ICollection<SubGroup> SubGroups {get; set;}
}
class SubGroup
{
public int Id {get; set;}
...
// every SubGroup has zero or more Product(one-to-many or many-to-many)
public virtual ICollection<Product> Products{get; set;}
// alas I don't know the return relation
// one-to-many: every SubGroup belongs to exactly one MainGroup using foreign key
public int MainGroupId {get; set;}
public virtual MainGroup MainGroup {get; set;}
// or every SubGroup has zero or more MainGroups:
public virtual ICollection<MainGroup> MainGroups {get; set;}
}
Нечто подобное для продукта:
class Product
{
public int Id {get; set;}
public bool? IsActive {get; set;} // might be a non-nullable property
...
// alas I don't know the return relation
// one-to-many: every Productbelongs to exactly one SubGroup using foreign key
public int SubGroupId {get; set;}
public virtual SubGroup SubGroup {get; set;}
// or every Product has zero or more SubGroups:
public virtual ICollection<SubGroup> SubGroups {get; set;}
}
И, конечно, ваш DbContext:
class MyDbContext : DbContext
{
public DbSet<MainGroup> MainGroups {get; set;}
public DbSet<SubGroup> SubGroups {get; set;}
public DbSet<Product> Products {get; set;}
}
Это все, что должна знать структура сущностей, чтобы обнаружить ваши таблицы, столбцы в ваших таблицах и отношения между таблицами (один ко многим, многие ко многим, один к нулю или один ). Только если вы хотите отклониться от стандартного именования, вам понадобятся атрибуты свободно распространяемого API.
В структуре сущностей столбцы таблиц представлены не виртуальными свойствами. Виртуальные свойства представляют отношения между таблицами (один ко многим, многие ко многим).
Обратите внимание, что хотя SubGroups
из MainGroup
объявлено как коллекция, если вы запросите SubGroups
из MaingGroup with Id 10
, вы все равно получите IQueryable.
Требования
При наличии запрашиваемой последовательности Products
и обнуляемого логического значения State
, HasProductsWithState(products, state)
должен возвращать запрашиваемую последовательность Products
, значение которой IsActive
равно State
При наличии запрашиваемой последовательности SubGroups
и обнуляемого логического значения State
, HasProductsWithState(subGroups, state)
должно возвращать запрашиваемую последовательность SubGroups
, у которой есть хотя бы один Product
, который "HasProductsWithState (Product, State) 1
При наличии запрашиваемой последовательности MainGroups
и обнуляемого логического значения State
, HasProductsWithState(mainGroups, state)
должно возвращать запрашиваемую последовательность MainGroups
, которая содержит все MainGroups
, которые имеют хотя бы одну SubGroup
, HasProductsWithState(SubGroup, State)
Решение
Ну, если вы напишите требования, подобные этим, методы расширения просты:
IQueryable<Product> WhereHasState(this IQueryable<Product> products, bool? state)
{
return products.Where(product => product.IsActive == state);
}
Поскольку эта функция не проверяет, имеет ли Продукт это состояние, а возвращает все Продукты, имеющие это состояние, я решил использовать другое имя.
bool HasAnyWithState(this IQueryable<Product> products, bool? state)
{
return products.WhereHasState(state).Any();
}
Ваш код будет немного отличаться, если IsActive является ненулевым свойством.
Я сделаю нечто подобное с SubGroups
:
IQueryable<SubGroup> WhereAnyProductHasState(this IQueryable<SubGroup> subGroups, bool? state)
{
return subgroups.Where(subGroup => subGroup.Products.HasAnyWithState(state));
}
bool HasProductsWithState(this IQueryable<SubGroup> subGroups, bool? state)
{
return subGroups.WhereAnyProductHasState(state).Any();
}
Что ж, вы уже знаете сверло для MainGroups
:
IQueryable<MainGroup> WhereAnyProductHasState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return maingroups.Where(mainGroup => mainGroup.SubGroups.HasProductsWithState(state));
}
bool HasProductsWithState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return mainGroups.WhereAnyProductHasState(state).Any();
}
Если вы посмотрите очень внимательно, вы увидите, что я не использовал какую-либо самоопределяемую функцию. Мои вызовы функций изменят только Expression
. Измененный Expression
можно перевести на SQL.
Я разделил функцию на множество мелких функций, потому что вы не сказали, хотите ли вы использовать HasProductsWithState(this IQueryable<SubGroup>, bool?)
и HasProductsWithState(this IQueryable<Product>, bool?)
.
TODO: сделать что-то похожее для аналога для HasChildrenWithName
: разделить на меньшие функции, которые содержат только функции LINQ, и ничего больше
Если вы будете вызывать только HasProductsWithState(this IQueryable<MainGroup>, bool?)
, вы можете сделать это в одной функции, используя `SelectMany:
IQueryable<MainGroup> HasProductsWithState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return mainGroups
.Where(mainGroup => mainGroup.SelectMany(mainGroup.SubGroups)
.SelectMany(subGroup => subGroup.Products)
.Where(product => product.IsActive == state)
.Any() );
}