【问题标题】:Entity-Framework Join ExplanationEntity-Framework Join 说明
【发布时间】:2015-09-15 15:29:49
【问题描述】:

我有以下实体

//Active Auction Entity
public class ActiveAuction
{
    public int Id { get; set; }

    public string Title { get; set; }

    public int? FirstAuctionId { get; set; }

    public int? SecondAuctionId { get; set; }

    public int? ThirdAuctionId { get; set; }

    public virtual Auction FirstAuction { get; set; }

    public virtual Auction SecondAuction { get; set; }

    public virtual Auction ThirdAuction { get; set; }
}

// Auction Entity
public class Auction
{
    public int AuctionId { get; set; }

    public AuctionStatus AuctionStatus { get; set; }

    public int? DepartmentId { get; set; }

    public virtual Department Department { get; set; }

}

// Department Entity
public class Department
{
    public int DepartmentId { get; set; }

    public string DepartmentName { get; set; }

    public int? AdminId { get; set; }

    public virtual Admin Admin { get; set; }
}

我要做的是让Active Auctions 加载Auctions 并且Auction 也加载Departments 我知道我应该为每个要加载的对象写Include,这样EF生成的SQL就会有join语句来为我选择对象

但这是现有的代码

 using (var dc = DataContext())
    {
        await dc.Auction
           .Include("Department.Admin")
           .Where(i => i.Id == id && i.AuctionStatus == AuctionStatus.Accepted).ToListAsync();

        return await dc.ActiveAuction.
           SingleOrDefaultAsync(i => i.Id == id);
    }

我不知道这段代码是如何工作的,返回的 ActiveAuctions 包含所有需要的对象

我检查了针对数据库执行的 SQL,并按预期生成了单独的查询。

我想要一个解释来了解返回的ActiveAcutions 是如何与其他提到的实体一起加载的!!?

【问题讨论】:

  • Categories.AdminDepartment.Admin 的错字吗?或者您是否从 Auction 对象中删除了 Categories 以缩短代码?
  • 它应该是 Department.Admin 我修正了,Admin 也是另一个现有实体
  • 很好,考虑使用强类型包含(即.Include(a => a.Department.Admin)。它将避免拼写错误/包含不存在的对象。
  • 回答您的问题,我相信这是因为您在相同的上下文中执行了这两个查询。 EF 然后意识到主键匹配,然后链接您的数据。
  • 我想过在内存中发生了某种合并,但是我找不到任何关于这个的文章

标签: c# entity-framework linq join


【解决方案1】:

原因很简单。您很可能知道,Entity Framework 跟踪您从数据库中获取的实体,主要是为了检测对它们的更改并在您调用 SaveChanges 时将这些更改应用到数据库。但是,这意味着到目前为止,EF 上下文具有从数据库中获取的某种实体缓存。

编辑:正如@GertArnold 在 cmets 中正确指出的那样——我对动态代理的解释完全错误——即使ProxyCreationEnabledfalse,它也可以这样工作。真正的原因是在调用DetectChanges 时由实体框架执行关系修复(它在各种事件上被隐式调用,例如将实体附加到上下文,或对DbSet 执行查询)。在该关系修复期间,EF 同步导航属性和外键,在您的情况下会导致您观察到的行为。更多关于关系修复in MSDN

要验证这一点,您可以使用以下简单代码:

using (var ctx = new TestEntities()) {
     ctx.Configuration.LazyLoadingEnabled = false;                
     ctx.Configuration.ProxyCreationEnabled = false;                
     var code = ctx.Codes.First();                
     var error = ctx.Errors.First();
     Debug.Assert(Object.ReferenceEquals(error.Code, code));                                
}

这里我首先获取一些实体(代码),然后获取另一个具有导航属性代码的实体(错误)。您会看到延迟加载已禁用。以下断言将通过,因为 error.Code 和 code 是同一个 .NET 对象,这确认它是从上下文缓存中获取的。

【讨论】:

  • 尽管进行了广泛的解释,但您并没有真正了解真正的原因:关系修复。发生这种情况不需要创建代理,所以恕我直言,这只会增加噪音。
  • @GertArnold 如果我理解正确,首先加载 Auction 和部门,然后加载 ActiveAuction,它不应该这样工作吗?
  • @GertArnold 感谢您指出这一点 - 实际上我对动态代理的解释完全错误。
  • 所以真正的原因是第一个查询(dc.Auction...)的结果恰好包含了第二个查询的ActiveAuction所引用的三个Auctions。
  • 是的,但我想我什至在使用动态代理的原始答案中也指出了这一点。
【解决方案2】:

您看到的行为是因为 EF 在内部维护每个数据库上下文实例的对象缓存。每次执行查询时,EF 首先检查内部缓存以查看它是否包含必要的实体。如果找到实体,则从缓存中返回,甚至不查询数据库。

在您的示例中会发生什么:

  • 第一个查询获取一些Auction 实体并将它们添加到缓存中;
  • 第二个查询被过滤ActiveAuction 实体并将它们添加到缓存中;
  • 当您检查第二个查询的结果时,您会看到属性已被填充,因为它们是在缓存中按键找到的;

您可以通过更改数据库上下文实例上的MergeOption 设置来更改此行为。此设置更改 EF 将结果从数据库合并到缓存的方式。

可能的值如下:

AppendOnly(默认值)

对象上下文中不存在的对象被附加到上下文中。如果对象已经在 上下文中,对象属性的当前值和原始值 条目不会被数据源值覆盖。的状态 对象的入口和入口中对象的属性状态 不变。 AppendOnly 是默认的合并选项。

无跟踪

对象保持在分离状态,在 对象状态管理器。但是,实体框架生成的实体和 具有代理的 POCO 实体维护对对象上下文的引用 方便加载相关对象。

覆盖更改

对象上下文中不存在的对象被附加到 语境。如果一个对象已经在上下文中,当前和 条目中对象属性的原始值被覆盖 与数据源值。对象条目的状态设置为 未更改,没有属性被标记为已修改。

PreserveChanges

对象上下文中不存在的对象被附加到 上下文。

(https://msdn.microsoft.com/en-us/library/system.data.objects.mergeoption(v=vs.110).aspx)

这是一篇很好的文章,它更详细地描述了所有选项,并包含一些代码示例。

http://www.develop1.net/public/post/Do-you-understand-MergeOptions.aspx

编辑:如果第一个查询对实体进行投影(例如,仅选择某些列),只是想添加默认行为可能会导致奇怪的结果。在这种情况下,即使只有一半加载,实体仍将被添加到缓存中。这意味着第二个查询也将返回一半加载的实体。

【讨论】:

  • ObjectContext API 已被弃用。
  • @GertArnold。没关系,因为 DBContext 只是 ObjectContext 的包装,所以原则保持不变。
【解决方案3】:

您不能使用 .Include("Department.Admin"),因为 EF 不会像您一样加载内部包含,例如 Admin

注意,当您首先使用EF代码时,最好确定表之间的真实关系,无论是在类中还是使用流利的API,以满足良好的设计而没有异常。因此,在ActiveAuction 中使用三个Auctions 而不进行任何澄清可能会打扰您。

另一件事是,您似乎在每个Auction 中都使用ActiveAuction 作为外键而没有任何声明!因此,将您的 Auction 类更改为:

public class Auction
{
    [ForeignKey("ActiveOne")]
    public int AuctionId { get; set; }

    public AuctionStatus AuctionStatus { get; set; }

    public int? DepartmentId { get; set; }

    public virtual Department Department { get; set; }

    [Required]
    public virtual ActiveAuction ActiveOne { get; set; } //match this name

}

考虑到这一点,我认为您的模型存在一些歧义。让我们谈谈查询。正如你所说:

我要做的是让ActiveAuctions 加载AuctionsAuction 也加载了Departments

所以你可以使用类似这样的查询:

await context.Auctions.Include("Department").Include("ActiveOne")
.Where(i => i.id == id && i.AuctionStatus == AuctionStatus.Accepted)
.Select(i => i.ActiveOne).ToListAsync();

【讨论】:

  • “你不能使用 .Include("Department.Admin") 因为 EF 不加载像你这样的内部包含”你是什么意思!?请解释一下。
  • 您不能在Include 中使用嵌套成员,或者至少它不能正常工作,我在使用EF 特别是Code first 大约有4 年的经验。
【解决方案4】:

当 EF 加载任何实体时,它会将它们与缓存中的现有实体挂钩,以便模型完整,所有引用都指向任何加载的对象。

【讨论】:

    【解决方案5】:

    “延迟加载是在第一次访问引用实体或实体的属性时自动从数据库加载实体或实体集合的过程。” 见这篇文章:https://msdn.microsoft.com/en-us/data/jj574232.aspx

    【讨论】:

    • 虽然 OP 的问题可能与延迟加载有关,但应该更多地解释它在这种特定情况下的实际工作方式。如您所见,这里没有Auction 和 ActiveAuction 之间的明确关系。第一个查询似乎只是加载与拍卖相关的所有内容,但 ActiveAuction(在第二个查询中使用)似乎填充了加载的数据自动
    • @Hopeless 如果启用了延迟加载,则 OP 可能会延迟加载它们(他没有注意到)......实际上,这是我第一次想到这个问题。
    • @Jcl 据我了解,延迟加载将加载通过导航属性访问的 相关对象。但是在这种情况下,您能否指出哪些对象的相关对象是什么?这里加载的对象是ActiveAuction,但它明确Auction相关吗?我在这里看不到任何关系或某种导航属性访问。这就是OP的问题令人困惑的地方。另外我不太确定延迟加载是否与这里有关,所以我只是要求反对这个答案以获得更多解释。
    • @Hopeless 也许我理解错了这个问题,但我认为 OP 的问题类似于:“为什么是 FirstAuctionSecondAuctionThirdAuction [及其相关的 Department 和 @ 987654328@properties] 当我在上下文中加载这些实体在不同的查询中时已经加载“......如果这是正确的,那么延迟加载可能确实是相关的(它似乎不是,基于cmets,但我了解它可能是如何相关的)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-08
    • 2022-01-13
    相关资源
    最近更新 更多