实体框架加入解释

时间:2015-09-15 15:29:49

标签: c# entity-framework linq join

我有以下实体

//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 AuctionsAuctions加载,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如何加载其他提到的实体!!?

5 个答案:

答案 0 :(得分:5)

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

编辑:正如@GertArnold在评论中正确指出的那样 - 我对动态代理的解释是完全错误的 - 即使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));                                
}

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

答案 1 :(得分:2)

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

在你的样本中,会发生什么:

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

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

可能的值如下:

  

AppendOnly(默认值)

     

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

     

NoTracking

     

对象保持处于“已分离”状态,并且不会在其中进行跟踪   ObjectStateManager。但是,实体框架生成的实体和   具有代理的POCO实体维护对对象上下文的引用   便于加载相关物品。

     

OverwriteChanges

     

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

     

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

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

答案 2 :(得分:0)

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

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

另一件事是这样的,看来你在ActiveAuction中使用Auction作为外键而没有任何声明!因此,将您的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的情况下获取Auctions   并且Auction也加载了Departments

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:-2)

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