我们将关系从必需更改为可选,现在由EF Core的Include()生成的结果SQL执行的是左外部联接,而不是内部联接。问题在于那些可选实体上具有必需的查询过滤器。
假设我们有以下内容;
public class First
{
public int? SecondId { get; set; }
}
public class Second
{
public First First { get; set; }
public int ThirdId { get; set; }
}
public class Third
{
public Second Second { get; set; }
public string Tenant { get; set; }
}
public class MyContext : DbContext
{
protected readonly string _tenant;
...
modelBuilder.Entity<Third>(p =>
{
p.HasQueryFilter(x => Tenant == _tenant);
});
...
}
然后我们执行以下操作:
MyContext.First.Include(p => p.Second).ThenInclude(p => p.Third);
由于该关系是可选的,因此这将产生一个左外部联接。然后,这当然会绕过查询过滤器。有没有办法让它包括一个INNER JOIN呢?
当前,这可以通过在以后的位置添加更多条件来解决:
.Where(p => p.Second.Third.Tenant == _tenant);
但这是不希望的,因为在某些情况下,_tenant为空,然后将提供错误的数据。
我知道我可以翻转它并继续前进
MyContext.Third.Include() ...
但这也是不可取的,因为在这种情况下,“第一”具有许多其他相关数据,并且我不想无休止地将Include()。ThenInclude()链接到荒谬之处。
我可以强制使用可选实体进行内部联接吗?还是我必须为此手动编写SQL?
答案 0 :(得分:2)
我可以强制使用可选实体进行内部联接吗?
不能。而你不应该。因为尽管inner join
可能会解决您的特殊情况,但通常它会过滤所有具有null
FK(例如First.SecondId == null
)的从属实体,这与 optional 的整个概念背道而驰。 em>关系。
我看到的问题是您似乎正在尝试使用Include
进行过滤。从概念上讲Include
是什么意思-对于查询返回的每个实体,还包括相关数据。既不打算过滤实体也不过滤相关数据。
因此,您需要的是查询过滤器。
实际的问题是EF Core Global Query Filters不支持criteria based on navigation properties。这就是为什么人们在这种情况下破坏规范化(引入冗余)并将TenantId
属性(列)放在每个实体(表)中,从而允许他们为每个实体设置全局过滤器。
话虽如此,显式查询过滤器(Where
)是目前唯一的选择。
.Where(p => p.Second.Third.Tenant == _tenant);
但这是不可取的,因为在某些情况下_tenant为空,然后将提供错误的数据。
好吧,例如,您只需要考虑可选关系的正确条件
.Where(p => p.SecondId == null || p.Second.Third.Tenant == _tenant);
但这实际上显示了每个实体上没有Tenant
的问题-当First.SecondId == null
时,您无法确定哪个Tenant
拥有First
。