.net EF Core 7:包含方法例外:无法转换,请将查询重写为可以转换的格式

wkftcu5l  于 2023-03-20  发布在  .NET
关注(0)|答案(1)|浏览(136)

我想编写一个LINQ查询,用于查找与特定课程具有最多intersect标记的10门课程。
我写了它,但是我得到了一个异常。我不想使用ToListAsEnumerable,因为我不想在得到10之前执行查询来获得所有课程。
我不想使用其他ORM。
代码:

var courses = await _dbContext.Courses
                              .AsNoTracking()
                              .Where(c => c.Id != request.Id)
                              .Select(c => new
                                      {
                                           Course = c,
                                           IntersectTagsCount = c.Tags.Sum(t => course.Tags.Contains(t) ? 1 : 0)
                                      })
                              .OrderByDescending(c => c.IntersectTagsCount)
                              .Take(10)
                              .ToListAsync();

我得到的错误是:
系统操作无效异常:LINQ表达式't =〉__course_Tags_1 .包含(t)?1:“0”未能转换。请以可以转换的形式重写查询,或者通过插入对“AsEnumerable”、"AsAsyncEnumerable“、”ToList“或”ToListAsync“的调用来显式切换到客户端计算。
实体如下所示:

它存储在数据库中,如下所示:

23c0lvtd

23c0lvtd1#

尝试将另一个实体或DTO插入查询表达式时会出现问题。(countrse.Tags)
给予这个。

var tagIds = course.Tags.Select(t => t.Id).ToList();

var courses = await _dbContext.Courses
    .AsNoTracking()
    .Where(c => c.Id != request.Id)
    .Select(c => new
    {
       Course = c,
       IntersectTagsCount = c.Tags.Sum(t => tagIds.Contains(t.Id) ? 1 : 0)
    })
    .OrderByDescending(c => c.IntersectTagsCount)
    .Take(10)
    .ToListAsync();

**编辑:**好吧,我没注意到你在实体中将标签存储为数组。不幸的是,你将无法转换一个Array.Sum,其中Tags属性可能需要通过一个值转换器向下转换为一个varchar,以构建一个逗号分隔的值来存储在DB中。

我的建议是,如果您需要能够按标签查询/合并课程,那么您应该规范化表结构,以便标签引用可以跨课程关联。类似这样的关系将是标准的多对多关系,其中我们创建一个标签表(TagId和TagName),然后创建一个CourseTags表(CourseId和TagId作为复合PK,FK返回到各自的表)
实体结构略有变化:

[Table("Courses")]
public class Course
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    // ...

    public virtual ICollection<Tag> Tags { get; set; } = new List<Tag>();
}

[Table("Tags")]
public class Tag
{
    [Key]
    public string Id { get; set; }
    
    public virtual ICollection<Course> Courses { get; set; } = new List<Course>();
}

EF可以完全在幕后管理CourseTags表。在上面的例子中,我只是使用标签名称/字符串作为ID/PK(即“TestTagName 1”),因为这个选项可能会减少重构使用的麻烦。如果预计会有很多标签,我建议使用标识整数作为ID,因为这将更容易索引。
现在假设您的客户端代码传递了一个过滤后的标签ID数组,以检查为course.tags(字符串数组),那么EF现在可以通过CourseTags关系在相关的Tag行之间构建查询:

var courses = await _dbContext.Courses
    .AsNoTracking()
    .Where(c => c.Id != request.Id)
    .Select(c => new
    {
       Course = c,
       IntersectTagsCount = c.Tags.Sum(t => course.Tags.Contains(t.Id) ? 1 : 0)
    })
    .OrderByDescending(c => c.IntersectTagsCount)
    .Take(10)
    .ToListAsync();

不同之处在于,EF在课程和标签之间具有关系,它可以围绕Linq操作构建查询,因为它知道课程将与一组标签记录相关联,而不是包含标签值附加在一起的单个列。
另一个可能适用于EF Core 7的选项是将标签作为JSON值集存储在课程中,因为我相信EF Core确实支持对JSON数据的查询。不幸的是,这不是我花时间修补的东西,但如果您决定确实更喜欢将标签存储在字段中而不是关系表中,则可能值得研究。

相关问题