【问题标题】:Left outer join one-to-one in Entity Framework实体框架中的左外连接一对一
【发布时间】:2016-09-09 16:02:04
【问题描述】:

我遇到了与 Entity Framework 6.1.3 中的一对一关系相关的问题。

我有 2 节课。

public class Package
{
    public Guid Id { get; set; }
    public string PackageNo { get; set; }
    public double SendingCost{ get; set; }
    public virtual PackageAppointment PackageAppointment { get; set; }
}

public class PackageAppointment 
{
    public DateTime Date { get; set; }
    public virtual Package Package { get; set; }
}

PackageAppointment 的地图类是:

public class PackageAppointmentMap
{
    public PackageAppointmentMap()
    {
        Property(x => x.Date).IsRequired();

        HasRequired(x => x.Package).WithOptional(x => x.PackageAppointment).Map(x => x.MapKey("PackageId"));

    }
}

一切正常。我对这段关系没有任何问题。问题是查询数据库。每次我使用上下文查询数据库中的Package 实体时,SQL Server 分析器都会生成类似这样的内容。

LINQ 查询:

var data = await Task.Run(() => GenericService.GetAll().Select(x => x.PackageNo));

SQL Server 分析器:

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[PackageNo] AS [PackageNo], 
    [Extent1].[SendingCost] AS [SendingCost],    
    [Extent2].[Id] AS [Id1]   
FROM    
    [dbo].[Package] AS [Extent1]
LEFT OUTER JOIN 
    [dbo].[PackageAppointment] AS [Extent2] ON [Extent1].[Id] = [Extent2].[PackageId]

我使用context.Package... 执行的每个查询都没有从PackageAppointment(或任何其他一对一关系)实体框架中检索数据,都会生成一个左外连接。

我项目中的实际情况是Package实体有5个一对一的关系。因此性能非常低。

也许我应该改变这种关系或别的什么。真的我不知道如何解决这个性能问题。

请指教。

【问题讨论】:

  • PackageAppointment实体的PK在哪里?
  • 分离数据库中的模型有好处吗?就我个人而言,我尽量不在数据库中建立任何真正的一对一关系,因为这通常表明希望在概念上将其拆分为业务逻辑(代码)而不是持久层。
  • 问题出在你的GetAll方法上,能发一下吗?
  • The actual condition in my project is that Package entity has 5 one to one relationships. The performance is very low because of this
  • 那么为什么我得到一个正确的 SQL 呢?可以提供minimal reproducible example吗?

标签: c# sql-server entity-framework linq


【解决方案1】:

您可以将 Package 和 PackageAppointment 之间的关系更改为:

public PackageAppointmentMap()
{
    Property(x => x.Date).IsRequired();

    HasOptional(x => x.Package)
        .WithOptionalPrincipal(x => x.PackageAppointment)
        .Map(x => x.MapKey("PackageAppointmentId"));
}

这将使您的 Package 表包含 FK PackageAppontmentId,并且您的查询将没有此连接。更改与 Package.HasRequired(x=>x.PackageAppointment) 的关系也将起作用(可能原因见下文)

之后访问包:

var query = dbContext.Packages.ToString();

返回:

SELECT
    1 AS [C1],
    [Extent1].[Id] AS [Id],
    [Extent1].[PackageNo] AS [PackageNo],
    [Extent1].[SendingCost] AS [SendingCost],
    [Extent1].[PackageAppointmentId] AS [PackageAppointmentId]
    FROM [dbo].[Packages] AS [Extent1]

另一方面,访问 PackageAppointmentss:

var anotherQuery = dbContext.PackageAppointments.ToString();

将导致查询:

SELECT
    1 AS [C1],
    [Extent1].[Id] AS [Id],
    [Extent1].[Date] AS [Date],
    [Extent2].[Id] AS [Id1]
    FROM  [dbo].[PackageAppointments] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Packages] AS [Extent2] ON ([Extent2].[PackageAppointmentId] IS NOT NULL) AND ([Extent1].[Id] = [Extent2].[PackageAppointmentId])

[编辑]

为什么会这样?

看起来这主要是为更改跟踪完成的。

场景:

您有一个PackageAppointment,其中包含一个必需的Package。这意味着,您的PackageAppointment 表具有外键PackageId,因此当您加载PackageAppointment 时,更改跟踪器具有有关PackageAppointment 的所有信息以跟踪对实体的所有可能更改,并且更改PackageAppointment.Package 引用是轻松跟踪 - 您在加载的实体中比较 PackageAppointment.PackageId,即使您没有真正从 Package 表中加载记录,这与 PackageAppointment 相关联(延迟加载 = 关闭)。

另一方面,如果您加载Package,您有一个属性PackageAppointment,您可以更改它以引用另一个PackageAppointment。但要跟踪此更改,您必须以某种方式获取与此包关联的PackageAppointment.Id。由于Package 表没有从自身到PackageAppointment 表的外键,因此您必须进行连接以获取与此Package 关联的PackageAppointment.Id。换句话说,如果您不执行此连接,则此实体的更改跟踪将无效,并且如果您对其进行任何更改,您将加载引用的PackageAppointment,这可能是无效的。

为了证明这一点,请尝试将.AsNoTracking() 用于此特定查询并检查返回的查询。

所以代码

var trackingQuery = dbContext.PackageAppointments.ToString();

Console.WriteLine("==========================");
Console.WriteLine("Query with Change Tracker enabled");
Console.WriteLine(trackingQuery);
Console.WriteLine("==========================");
Console.WriteLine("\n\n");

var noTrackingQuery = dbContext.PackageAppointments.AsNoTracking().ToString();

Console.WriteLine("==========================");
Console.WriteLine("Query with Change Tracker disabled");
Console.WriteLine(noTrackingQuery);
Console.WriteLine("==========================");

结果输出如下:

==========================
Query with Change Tracker enabled
SELECT
    1 AS [C1],
    [Extent1].[Id] AS [Id],
    [Extent1].[Date] AS [Date],
    [Extent2].[Id] AS [Id1]
    FROM  [dbo].[PackageAppointments] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Packages] AS [Extent2] ON ([Extent2].[PackageAppointmentId] IS NOT NULL) AND ([Extent1].[Id] = [Extent2].[PackageAppointmentId])
==========================



==========================
Query with Change Tracker disabled
SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Date] AS [Date]
    FROM [dbo].[PackageAppointments] AS [Extent1]
==========================

如您所见,LEFT OUTER JOIN 在这里神奇地消失了,这证明这与 Change Tracking 系统有关。

【讨论】:

  • 如果我改变了关系,这意味着我正在改变逻辑。这不是我在这里的目的。这里的关键是“为什么会发生这种情况?可能是延迟加载支持”。我不确定,但我认为懒惰不是问题。我不知道为什么会这样。这就是我想知道的。如果您不需要这些数据,我认为没有必要生成一个左外连接。
  • @Rayko 我编辑了帖子,现在看看它的底部。
猜你喜欢
  • 1970-01-01
  • 2011-07-29
  • 1970-01-01
  • 1970-01-01
  • 2013-03-15
  • 1970-01-01
  • 1970-01-01
  • 2021-11-18
  • 1970-01-01
相关资源
最近更新 更多