如何使用Entity Framework Core中的导航属性获取内部联接

时间:2018-08-21 15:56:08

标签: c# entity-framework-core

我想生成一个查询,该查询使用导航属性将内部联接用于一对一关系。这是针对我无法更改的现有数据库。

这是我的实体的样子:

[Table("Info")]
public class Info
{
    [Key]
    public int InfoId { get; set; }
    public string Name { get; set; }   
    public virtual MoreInfo MoreInfo { get; set; }
}

[Table("MoreInfo")]
public class MoreInfo
{
    [Key]
    public int InfoId { get; set; }
    public string OtherName { get; set; }
    public int OtherNumber { get; set; }
}

这是我的查询:

var x = await context.Infos
    .Select(info => new
    {
        info.InfoId,
        info.Name,
        info.MoreInfo.OtherName,
        info.MoreInfo.OtherNumber
    })
    .ToListAsync();

这将产生此SQL(使用左连接):

SELECT [info].[InfoId], [info].[Name], [info.MoreInfo].[OtherName], [info.MoreInfo].[OtherNumber]
FROM [Info] AS [info]
LEFT JOIN [MoreInfo] AS [info.MoreInfo] ON [info].[InfoId] = [info.MoreInfo].[InfoId]

如果Info中的记录不在MoreInfo中,这将导致错误,这就是为什么我要进行内部联接。

我在OnModelCreating中尝试了各种选项,例如

modelBuilder.Entity<Info>()
    .HasOne(p => p.MoreInfo)
    .WithOne()
    .IsRequired();

或将导航属性设置为“必需”

[Table("Info")]
public class Info
{
    [Key]
    public int InfoId { get; set; }
    public string Name { get; set; }
    [Required]
    public virtual MoreInfo MoreInfo { get; set; }
}

或在查询中使用包含

var x = await context.Infos
    .Include(info => info.MoreInfo)
    .Select(info => new
    {
        info.InfoId,
        info.Name,
        info.MoreInfo.OtherName,
        info.MoreInfo.OtherNumber
    })
    .ToListAsync();

但这些都没有更改联接。

我能够通过使用join函数而不是使用navigation属性来获得内部联接,但是我想尽可能避免这种情况。语法很丑陋,需要我将MoreInfos添加到上下文中。如果可能的话,我想使用导航属性创建一个内部联接。

var x = await context.Infos
    .Join(context.MoreInfos, info => info.InfoId, moreInfo => moreInfo.InfoId, (info, moreInfo) =>
        new
        {
            info.InfoId,
            info.Name,
            moreInfo.OtherName,
            moreInfo.OtherNumber
        })
    .ToListAsync();

产生正确的SQL

SELECT [info].[InfoId], [info].[Name], [moreInfo].[OtherName], [moreInfo].[OtherNumber]
FROM [Info] AS [info]
INNER JOIN [MoreInfo] AS [moreInfo] ON [info].[InfoId] = [moreInfo].[InfoId]

但是我宁愿使用Navigation属性。

1 个答案:

答案 0 :(得分:1)

当前(EF Core 2.1.2)无法使用提供的模型。

从技术上讲,无法实施真正的一对一关系,因此EF Core IsRequired表示从属实体FK是否可为空。对于像您这样的共享PK关联,哪个始终是正确的。请注意,Info是主体,而MoreInfo是依赖者,因此MoreInfo要求Info,反之则不然,这就是EF Core在导航时生成左外部联接的原因从委托人到受抚养人。

您可以做的一件事是包括不为空的条件:

.Where(info => info.MoreInfo != null)

在EF6查询中使用(带有一些中间投影)可以使EF6查询转换器使用内部联接。 EF Core仍然不够智能,因此所有这些技巧都不起作用。它使用右侧键IS NOT NULL过滤器生成左外部联接。这等效于内部联接,并且可能会由SQL查询优化器这样处理。但是还是...

当前获得内部联接的唯一方法(当然是手动联接除外)是从关系的 other 端导航,即从依赖关系导航到 required 委托人。无需从上下文中公开DbSet<MoreInfo>Set<TEntity>()方法可用于访问任何实体集。不过,您需要一个反向导航属性:

public class MoreInfo
{
    // ...
    public virtual Info Info { get; set; }
}

当然还有更新的流利配置:

modelBuilder.Entity<Info>()
    .HasOne(e => e.MoreInfo)
    .WithOne(e => e.Info)
    .HasForeignKey<MoreInfo>(e => e.InfoId);

现在查询可以重写为:

var x = await context.Set<MoreInfo>()
    .Select(moreInfo => new
    {
        moreInfo.Info.InfoId,
        moreInfo.Info.Name,
        moreInfo.OtherName,
        moreInfo.OtherNumber
    })
    .ToListAsync();

这将生成所需的内部联接:

SELECT [moreInfo].[InfoId], [moreInfo.Info].[Name], [moreInfo].[OtherName], [moreInfo].[OtherNumber]
FROM [MoreInfo] AS [moreInfo]
INNER JOIN [Info] AS [moreInfo.Info] ON [moreInfo].[InfoId] = [moreInfo.Info].[InfoId]