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 23 的文章,因为它们都包含 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);