关于c#:在可选关系上从EF Cores Include()强制进行内部联接

Force inner join from EF Cores Include() on optional relationship

我们将关系从必需更改为可选,现在由EF Core的Include()生成的结果SQL执行的是左外部联接,而不是内部联接。问题在于那些可选实体上具有必需的查询过滤器。

假设我们有以下内容;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
    });
    ...
}

然后我们执行以下操作:

1
MyContext.First.Include(p => p.Second).ThenInclude(p => p.Third);

由于该关系是可选的,因此这将产生一个LEFT OUTER JOIN。然后,这当然会绕过查询过滤器。有没有办法使它包括一个INNER JOIN呢?

当前,这可以通过在稍后的位置添加更多条件来解决:

1
.Where(p => p.Second.Third.Tenant == _tenant);

但这是不希望的,因为在某些情况下,_tenant为空,然后将提供错误的数据。

我知道我可以翻转它并去

1
MyContext.Third.Include() ...

但这也是不可取的,因为在这种情况下,First首先有很多其他相关数据,我不想无休止地将Include()。ThenInclude()链接到荒谬的地步。

我可以强制使用可选实体进行内部联接吗?还是我必须为此手动编写SQL?


Can I force inner joins with optional entities?

不能。而且你不应该。因为虽然inner join可能会解决您的特殊情况,但通常它会过滤所有具有null FK(例如First.SecondId == null)的从属实体,这与可选关系的整个概念背道而驰。

我看到的问题是您似乎正在尝试使用Include进行过滤。从概念上讲Include是什么意思-对于查询返回的每个实体,还包括相关数据。它既不打算过滤实体也不过滤相关数据。

因此,您需要的是查询过滤器。

实际的问题是EF Core全局查询过滤器不支持基于导航属性的条件。这就是为什么这种情况下的人们破坏规范化(引入冗余)并将TenantId属性(列)放在每个实体(表)中,从而允许他们为每个实体设置全局过滤器的原因。

话虽这么说,显式查询过滤器(Where)是当前唯一的选择。

1
   .Where(p => p.Second.Third.Tenant == _tenant);

But this is undesirable since in some edge cases the _tenant is null and will then give the wrong data.

好吧,您只需要考虑可选关系的正确条件即可,例如

1
.Where(p => p.SecondId == null || p.Second.Third.Tenant == _tenant);

但这实际上显示了每个实体上都没有Tenant的问题-当First.SecondId == null时,您无法确定哪个Tenant拥有First