.NET Core 自访问查询过滤器

joh*_*y 5 3 c# entity-framework-core .net-core .net-core-3.1 ef-core-3.1

我正在寻找解决EntityFramework Core Bug 的方法。我正在尝试编写一个对自身进行过滤的查询。

免责声明: 我正在做一些比通过显式过滤更复杂的事情userId,为了简单起见,我只是将它与硬编码值一起使用,因为确切的实现与我的问题无关。

protected override void OnModelCreating(ModelBuilder modelBuilder) =>
    modelBuilder.Entity<ConversationSubscription>()
        .HasQueryFilter(x => x.Conversation.ConversationSubscriptions
            .Select(c => c.UserId).Contains(315)
        );
Run Code Online (Sandbox Code Playgroud)

由于查询过滤器试图访问它正在过滤的实体,因此它最终陷入无限循环。由于我们使用的是ModelBuilder而非DbSet,因此无法将其标记为IgnoreQueryFilters

鉴于此,我尝试使用当前上下文来过滤自身:

modelBuilder.Entity<ConversationSubscription>().HasQueryFilter(x => 
    this.Set<ConversationSubscription().AsNoTracking().IgnoreQueryFilters()
        .Where(cs => cs.ConversationId == x.ConversationId)
            .Select(c => c.UserId)
            .Contains(315)
);
Run Code Online (Sandbox Code Playgroud)

然而,这会抛出一个InvalidOperationException,很可能是因为我们试图在OnModelCreating完成之前使用上下文。

我觉得有一种方法可以解决这个问题,如果我能以某种方式选择ConversationSubsriptions匿名类型,这样它们就不会被过滤。

编辑:我试图使用匿名类型来解决这个问题,但没有运气。

modelBuilder.Entity<ConversationSubscription>().HasQueryFilter(c =>
    x => x.Conversation.Messages.Select(m => new {
        Value = m.Conversation.ConversationSubscriptions.Distinct()
            .Select(cs => cs.UserId).Contains(c.Variable(this._userId)) 
    }).FirstOrDefault().Value
);
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 5

查询过滤器最初不支持访问导航属性或数据库集。看起来 EF Core 3.0 删除了这些限制(可能是因为每个 LINQ 查询模式的新单 SQL 语句),具有以下限制/错误:

  1. AsNoTracking()并且AsTracking()- 不支持,这是有道理的,因为查询过滤器总是被转换成 SQL。

  2. Include/ ThenInclude- 允许,但由于同样的原因被忽略。

  3. IgnoreQueryFilters- 不支持。这可以被视为错误,因为它可以用于解决下一个案例。

  4. 通过导航属性或数据库集交叉引用过滤器(例如,实体A过滤器使用实体B,实体B过滤器使用实体A) - 原因StackOverflowException是过滤器试图相互使用。这是一个错误。

  5. 通过导航属性的自引用过滤器 - 与 #4 相同的错误,应该像 #6。

  6. 通过数据库集的自引用过滤器 - 支持(!),在过滤器子查询中总是被忽略。

话虽如此,幸运的是您的案例得到了#6 的支持,即您的第二次尝试只是不受支持AsNoTracking()IgnoreQueryFilters()已删除:

modelBuilder.Entity<ConversationSubscription>().HasQueryFilter(x => 
    this.Set<ConversationSubscription()
        .Where(cs => cs.ConversationId == x.ConversationId)
              .Select(c => c.UserId)
              .Contains(315));
Run Code Online (Sandbox Code Playgroud)