我有以下实体
//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
如何加载其他提到的实体!!?
答案 0 :(得分:5)
原因很简单。您很可能知道,Entity Framework会跟踪您从数据库中获取的实体,主要用于检测对它们的更改,并在您调用SaveChanges
时将这些更改应用于数据库。但是,这意味着到目前为止,EF上下文具有从数据库中提取的实体缓存。
编辑:正如@GertArnold在评论中正确指出的那样 - 我对动态代理的解释是完全错误的 - 即使ProxyCreationEnabled
为false
,它也会以这种方式运行。真正的原因是在调用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