SQLite 和 LINQ:查找具有子列表的所有对象,其中所有 ID 都出现在提供的 ID 列表中
SQLite and LINQ: find all objects that have a sub list with all ids present in a supplied list of IDs
我有以下 class:
public class Article
{
long Id;
List<Category> Categories;
}
我正在使用 EF Core 5,我需要的是针对 SQLite 的 LINQ 查询,return所有文章都具有我指定的所有类别。
我尝试了以下代码:
List<long> cIds = c.Select (x => x.Id).ToList ();
query.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId)));
但是编译器说
InvalidOperationException: The LINQ expression 'DbSet<Article>()
.Where(a => __cIds_0
.All(cId => DbSet<Dictionary<string, object>>("ArticleCategory")
.Where(a0 => EF.Property<Nullable<long>>(a, "Id") != null && object.Equals(
objA: (object)EF.Property<Nullable<long>>(a, "Id"),
objB: (object)EF.Property<Nullable<long>>(a0, "ArticlesId")))
.Join(
inner: DbSet<Category>(),
outerKeySelector: a0 => EF.Property<Nullable<long>>(a0, "CategoriesId"),
innerKeySelector: c => EF.Property<Nullable<long>>(c, "Id"),
resultSelector: (a0, c) => new TransparentIdentifier<Dictionary<string, object>, Category>(
Outer = a0,
Inner = c
))
.Select(ti => ti.Inner.Id)
.Any(p => p == cId)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
如何获得?
我发现的一个可能的解决方法如下:
List<long> cIds = c.Select (x => x.Id).ToList ();
query = query.Where (art => art.Categories.Select (c => c.Id).Any (x => cIds.Contains (x)));
query = query.Include (x => x.Categories);
result = await query.ToListAsync ();
result = result.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId))).ToList ();
但我想知道是否可以使用单个 LINQ 查询获得相同的结果。
提前致谢
更新:
我将在将使用此代码的地方添加函数,并制作一个示例以使事情更清楚:
这是将使用代码的函数:
public async Task<List<Article>> SearchAsync (string search, Section s, Website w,
List<Category> c)
{
List<Article> result = new List<Article> ();
if (
search == ""
&& s == null
&& w == null
&& c.Count == 0
)
return result;
IQueryable<Article> query = dbSet.AsQueryable ();
if (search != "")
query = query.Where (x => x.Title.Contains (search) || x.Summary.Contains (search));
if (s != null)
query = query.Where (x => x.SectionId == s.Id);
if (w != null)
query = query.Where (x => x.WebsiteId == w.Id);
if (c.Count > 0)
{
List<long> cIds = c.Select (x => x.Id).ToList ();
query = query.Where (art => art.Categories.Select (c => c.Id).Any (x => cIds.Contains (x)));
}
query = query.Include (x => x.Categories);
result = await query.ToListAsync ();
if (c.Count > 0)
{
List<long> cIds = c.Select (x => x.Id).ToList ();
result = result.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId))).ToList ();
}
return result;
}
这是一个例子:
假设 c
将包含 ID 9,10,11
并且文章集合是以下伪代码:
List<article> articles = new List<Article> ()
{
new Article () {Id = 1, Categories = "12,44,55"}
new Article () {Id = 2, Categories = "7,8,9,10,11"}
new Article () {Id = 3, Categories = "9,10,11"}
}
linq 查询应该 return 带有 ID 2
和 3
的文章,因为它们都包含 c
.
中存在的所有 ID
What I need is a LINQ query against SQLite that returns all the articles that have all the categories that I specify.
所以你有一个 Category
ID 序列,你想要所有 Articles
,每篇文章只包含你的类别 ID 序列中的类别。
我不确定你的变量 'c' 是什么,但在我看来,以下语句 returns 所有 c
:
的 ID
List<long> cIds = c.Select (x => x.Id).ToList ();
如果 c
是您的类别序列,那么您将拥有所有现有类别的 ID。这意味着您将拥有所有文章,每篇文章都有所有类别。
如果您有一个本地类别 ID 序列,并且数量有限(比如大约 250),那么您应该使用 Contains
:
IEnumerable<long> categoryIds = ...
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => categoryIds.Contains(category.Id)
.ToList(),
})
因此,如果您的类别 ID 为 2、3 和 12,此查询将为您提供所有仅包含 ID 为 2、3、12 的类别的文章。
如果第 40 条只有类别 20、21、21,那么第 40 条将出现在您的结果中,但它的类别列表为空。
如果您在本地没有您的类别 ID,但您有一个 select 类别 ID 的谓词,那么您的查询将类似于:
IQueryable<long> categoryIds = dbContext.Categories
.Where(category => category.Status == StatusCode.Obsolete); // predicate
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => categoryIds.Contains(category.Id)
.ToList(),
});
因为您的第一个查询是 IQueryable<...>
,所以它还没有执行。如果你愿意,你可以把它变成一个大声明:
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => dbContext.Categories
.Where(category => category.Status == StatusCode.Obsolete)
.Contains(category.Id))
.ToList(),
});
虽然这不会提高效率,但肯定会降低可读性。
使用Intersect
的解决方案之一,但我们必须准备交集数据。
// articles query
var query = ...
var cIds = c.Select(x => x.Id).ToList();
var idsCount = cIds.Count();
// translating list of IDs to IQueryable
var categoryIdsQuery = dbContext.Categories
.Where(c => cIds.Contains(c.Id))
.Select(c => c.Id);
query = query
.Where(art => art.Categories
.Select(c => c.Id)
.Intersect(categoryIdsQuery)
.Count() == idsCount
)
.Include(x => x.Categories);
我有以下 class:
public class Article
{
long Id;
List<Category> Categories;
}
我正在使用 EF Core 5,我需要的是针对 SQLite 的 LINQ 查询,return所有文章都具有我指定的所有类别。
我尝试了以下代码:
List<long> cIds = c.Select (x => x.Id).ToList ();
query.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId)));
但是编译器说
InvalidOperationException: The LINQ expression 'DbSet<Article>()
.Where(a => __cIds_0
.All(cId => DbSet<Dictionary<string, object>>("ArticleCategory")
.Where(a0 => EF.Property<Nullable<long>>(a, "Id") != null && object.Equals(
objA: (object)EF.Property<Nullable<long>>(a, "Id"),
objB: (object)EF.Property<Nullable<long>>(a0, "ArticlesId")))
.Join(
inner: DbSet<Category>(),
outerKeySelector: a0 => EF.Property<Nullable<long>>(a0, "CategoriesId"),
innerKeySelector: c => EF.Property<Nullable<long>>(c, "Id"),
resultSelector: (a0, c) => new TransparentIdentifier<Dictionary<string, object>, Category>(
Outer = a0,
Inner = c
))
.Select(ti => ti.Inner.Id)
.Any(p => p == cId)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
如何获得?
我发现的一个可能的解决方法如下:
List<long> cIds = c.Select (x => x.Id).ToList ();
query = query.Where (art => art.Categories.Select (c => c.Id).Any (x => cIds.Contains (x)));
query = query.Include (x => x.Categories);
result = await query.ToListAsync ();
result = result.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId))).ToList ();
但我想知道是否可以使用单个 LINQ 查询获得相同的结果。
提前致谢
更新:
我将在将使用此代码的地方添加函数,并制作一个示例以使事情更清楚:
这是将使用代码的函数:
public async Task<List<Article>> SearchAsync (string search, Section s, Website w,
List<Category> c)
{
List<Article> result = new List<Article> ();
if (
search == ""
&& s == null
&& w == null
&& c.Count == 0
)
return result;
IQueryable<Article> query = dbSet.AsQueryable ();
if (search != "")
query = query.Where (x => x.Title.Contains (search) || x.Summary.Contains (search));
if (s != null)
query = query.Where (x => x.SectionId == s.Id);
if (w != null)
query = query.Where (x => x.WebsiteId == w.Id);
if (c.Count > 0)
{
List<long> cIds = c.Select (x => x.Id).ToList ();
query = query.Where (art => art.Categories.Select (c => c.Id).Any (x => cIds.Contains (x)));
}
query = query.Include (x => x.Categories);
result = await query.ToListAsync ();
if (c.Count > 0)
{
List<long> cIds = c.Select (x => x.Id).ToList ();
result = result.Where (art => cIds.All (cId => art.Categories.Select (c => c.Id).Contains (cId))).ToList ();
}
return result;
}
这是一个例子:
假设 c
将包含 ID 9,10,11
并且文章集合是以下伪代码:
List<article> articles = new List<Article> ()
{
new Article () {Id = 1, Categories = "12,44,55"}
new Article () {Id = 2, Categories = "7,8,9,10,11"}
new Article () {Id = 3, Categories = "9,10,11"}
}
linq 查询应该 return 带有 ID 2
和 3
的文章,因为它们都包含 c
.
What I need is a LINQ query against SQLite that returns all the articles that have all the categories that I specify.
所以你有一个 Category
ID 序列,你想要所有 Articles
,每篇文章只包含你的类别 ID 序列中的类别。
我不确定你的变量 'c' 是什么,但在我看来,以下语句 returns 所有 c
:
List<long> cIds = c.Select (x => x.Id).ToList ();
如果 c
是您的类别序列,那么您将拥有所有现有类别的 ID。这意味着您将拥有所有文章,每篇文章都有所有类别。
如果您有一个本地类别 ID 序列,并且数量有限(比如大约 250),那么您应该使用 Contains
:
IEnumerable<long> categoryIds = ...
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => categoryIds.Contains(category.Id)
.ToList(),
})
因此,如果您的类别 ID 为 2、3 和 12,此查询将为您提供所有仅包含 ID 为 2、3、12 的类别的文章。
如果第 40 条只有类别 20、21、21,那么第 40 条将出现在您的结果中,但它的类别列表为空。
如果您在本地没有您的类别 ID,但您有一个 select 类别 ID 的谓词,那么您的查询将类似于:
IQueryable<long> categoryIds = dbContext.Categories
.Where(category => category.Status == StatusCode.Obsolete); // predicate
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => categoryIds.Contains(category.Id)
.ToList(),
});
因为您的第一个查询是 IQueryable<...>
,所以它还没有执行。如果你愿意,你可以把它变成一个大声明:
var articlesWithTheseCategories = dbContext.Articles.Select(article => new
{
Id = article.Id,
Categories = article.Categories
.Where(category => dbContext.Categories
.Where(category => category.Status == StatusCode.Obsolete)
.Contains(category.Id))
.ToList(),
});
虽然这不会提高效率,但肯定会降低可读性。
使用Intersect
的解决方案之一,但我们必须准备交集数据。
// articles query
var query = ...
var cIds = c.Select(x => x.Id).ToList();
var idsCount = cIds.Count();
// translating list of IDs to IQueryable
var categoryIdsQuery = dbContext.Categories
.Where(c => cIds.Contains(c.Id))
.Select(c => c.Id);
query = query
.Where(art => art.Categories
.Select(c => c.Id)
.Intersect(categoryIdsQuery)
.Count() == idsCount
)
.Include(x => x.Categories);