.NET Core 自访问查询过滤器

.NET Core Self Accessing QueryFilters

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

Disclaimer: I'm doing something a bit more complex than filtering by an explicit userId, I am just using this with a hard-coded value for simplicity, as the exact implementation isn't relevant to my question.

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

由于查询过滤器正在尝试访问它正在过滤的实体,因此它最终陷入无限循环。由于我们使用的是 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)
);

但是,这会引发 InvalidOperationException,很可能是因为我们试图在 OnModelCreating 完成之前使用上下文。

我觉得如果我能以某种方式 select 将 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
);

也许我在这里遗漏了一些明显的东西,约翰尼,所以如果我离题太远,我深表歉意。

但我觉得你在这样做:

  • From conversation subscriptions, join conversations, then join conversation subscriptions where user id = 315

当你could/should这样做时:

  • From conversation subscriptions where user id = 315, join conversations.

我看不出你的查询中的往返是什么,因为看起来你是从 ConversationSubscription 查询。在这样的查询中,一个简单的包含将 return 仅包含用户有权访问的对话和对话订阅。这不是你想要的吗return?

var result = context.ConversationSubscriptions.Include(c => c.Conversation).ToList();

private int _userId = 315;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<ConversationSubscription>()
        .HasQueryFilter(x => x.UserId.Contains(_userId));
}

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

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

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

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

  4. 通过导航属性或数据库集 - 导致 WhosebugException 因为过滤器正在尝试相互使用。这是一个错误。

  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));