.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:
AsNoTracking()
和 AsTracking()
- 不支持,这是有道理的,因为查询过滤器总是转换为 SQL.
Include
/ ThenInclude
- 允许,但由于同样的原因被忽略。
IgnoreQueryFilters
- 不支持。这可以被视为错误,因为它可以用于解决下一个案例。
通过导航属性或数据库集 - 导致 WhosebugException
因为过滤器正在尝试相互使用。这是一个错误。
通过导航属性自引用过滤器 - 与 #4 相同的错误,应该与 #6 类似。
通过数据库集的自引用过滤器 - 支持(!),在过滤器子查询中总是被忽略。
综上所述,幸运的是您的案例得到了#6 的支持,即您的第二次尝试只删除了不受支持的 AsNoTracking()
和 IgnoreQueryFilters()
:
modelBuilder.Entity<ConversationSubscription>().HasQueryFilter(x =>
this.Set<ConversationSubscription()
.Where(cs => cs.ConversationId == x.ConversationId)
.Select(c => c.UserId)
.Contains(315));
我正在寻找 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:
AsNoTracking()
和AsTracking()
- 不支持,这是有道理的,因为查询过滤器总是转换为 SQL.Include
/ThenInclude
- 允许,但由于同样的原因被忽略。IgnoreQueryFilters
- 不支持。这可以被视为错误,因为它可以用于解决下一个案例。通过导航属性或数据库集 - 导致
WhosebugException
因为过滤器正在尝试相互使用。这是一个错误。通过导航属性自引用过滤器 - 与 #4 相同的错误,应该与 #6 类似。
通过数据库集的自引用过滤器 - 支持(!),在过滤器子查询中总是被忽略。
综上所述,幸运的是您的案例得到了#6 的支持,即您的第二次尝试只删除了不受支持的 AsNoTracking()
和 IgnoreQueryFilters()
:
modelBuilder.Entity<ConversationSubscription>().HasQueryFilter(x =>
this.Set<ConversationSubscription()
.Where(cs => cs.ConversationId == x.ConversationId)
.Select(c => c.UserId)
.Contains(315));