实体框架始终包含上下文中的数据,即使我不要求它

Posted

技术标签:

【中文标题】实体框架始终包含上下文中的数据,即使我不要求它【英文标题】:Entity Framework always includes data that is in context even if I don't ask for it 【发布时间】:2012-11-21 09:49:39 【问题描述】:

我首先使用 MVC.NET web api、EF 和 DB,并且我在上下文中关闭了延迟加载。即使关闭了 LazyLoading,EF 也会返回太多数据。

例如,我有一个角色的用户。当我查询用户并包含角色时,Role.Users 属性会自动填充数据,因为用户已加载到上下文中。

为什么我不能让 EF 满足我的要求?还是我在这里错过了什么大事?

public partial class User

    public int UserID  get; set; 
    public string Title  get; set; 
    public string Email  get; set; 
    public int RoleID  get; set; 

    ....

    public virtual Role Role  get; set; 
 

public partial class Role

    public int RoleID  get; set; 
    public string RoleName  get; set; 

    ....

    public virtual ICollection<User> Users  get; set; 
 




return db.Users.Include(u => u.Role);
// ^^ user.Role.Users is filled with 1000s of users

TL;DR - 我希望 EF 永远不要将数据加载到导航属性/集合中,除非我直接 .Include() 它。序列化为 JSON 时,我只想要我明确要求的内容。似乎即使关闭延迟加载,已经在上下文中的导航属性(即通常是“循环引用”)也会被加载并返回。

【问题讨论】:

with LazyLoading turned off -- 呃,这不是急于加载吗? 只是一个小提示。延迟加载意味着它只会在需要时加载它需要的内容......关闭它对于您正在寻找的内容是违反直觉的。 你能发布代码吗 - 你的模型加上数据访问代码。 据我了解和测试 - 延迟加载意味着当您访问作为集合的实体上的属性时,它会加载该集合。我把它关了,因为我不希望它加载除了我告诉它包含的任何东西。我的问题是,即使关闭此功能,它仍然包含已加载到上下文中的集合,因此用户有一个角色,并且角色有许多用户作为导航属性,并且它已经加载了 1000 多个用户,即使我没有告诉 EF 包含它。 @chris1234p 不,你告诉它不要懒惰......“不懒惰”意味着它应该一次加载所有内容,因此不要等到可能需要它(第一次访问集合时) ...这又是“急切加载”(与“延迟加载”相反)。 【参考方案1】:

首先:开启延迟加载。

第二:如果你想过滤你检索和返回的内容,然后做一个自定义的返回对象或其他东西。

from u in db.Users
join r in db.Roles
  on u.RoleID equals r.RoleID
select new  u.UserID, u.Title, u.Email, r.RoleName 

或者类似的东西。您将有一个最小的返回对象,并且您的对象图会很小。

【讨论】:

使用延迟加载将返回导航属性,因为它们已被序列化程序触及。【参考方案2】:

除了我告诉它包含的内容之外,我不希望它加载任何内容。

您似乎需要使用Explicit Loading。基本上,您可以像这样加载特定实体:

context.Include("Roles")

据我所知,不应包括相关实体。确实应该禁用延迟加载,您可以使用Load 显式加载导航属性。

【讨论】:

如果部分要求是希望对象仍附加到上下文并因此可更新等,那么这是比我下面的更好的解决方案。确保您始终包含您需要的东西;如果你这样做,我同意 Serge,这可能会让你更接近你想要的。 你好 Serge,这就是我正在做的事情,但似乎为了让它只包含我告诉它包含的内容,我需要使用一个全新的上下文。否则,如果数据已经在上下文中,它将被加载到返回的数据中,即使我不要求它。就像它试图通过给我一些我不想要的东西来“帮助”我。【参考方案3】:

您可以使用Select() 仅选择您需要的内容。

var users = _db.Users.Select(x => new

    UserID = x.UserID,
    Title = x.Title,
    Email = x.Email,
    RoleID = x.RoleID
).AsEnumerable();

【讨论】:

【参考方案4】:

您看到的行为称为 Relationship Fixup,您无法禁用它。

如果您正在加载具有角色的用户以序列化它们并将它们发送到某个地方,我猜您不想在它们已加载的上下文中跟踪实体的更改。因此,无需将它们附加到上下文,您可以使用:

return db.Users.Include(u => u.Role).AsNoTracking();

或者按照@STLRick 的建议,使用投影到专门用于序列化的对象中。

【讨论】:

由于某种原因,当我尝试 AsNoTracking 时,我会在序列化过程中出错。 @chris1234p:也许会问一个新问题并描述这些错误。我专注于您的问题,如何避免填充实体之间的所有导航属性。【参考方案5】:

您是对的,开启延迟加载后,您将返回导航属性,因为它们被序列化程序“触摸”,导致它们被加载。如果您希望属性返回为空,则应关闭延迟加载。也就是说,一旦实体被加载到上下文中(例如,通过其他查询),它们“似乎”就会被序列化程序处理。所以答案是告诉序列化器不要返回导航属性。我能找到的最好方法是使用 DTO(数据传输对象)。这使您可以准确地返回您想要的数据,而不是您的实际实体。

您的 DTO 可能如下所示:

public partial class UserDto

    public UserDto(user User)
    
        UserID = user.UserID;
        Title = user.Title;
        //... and so on
    
    public int UserID  get; set; 
    public string Title  get; set; 
    public string Email  get; set; 
    public int RoleID  get; set; 

    //exclude the Role navigation property from your DTO

...然后你可以这样做:

return db.Users.Include(u => u.Role).Select(user => new UserDto(user));

【讨论】:

以上是关于实体框架始终包含上下文中的数据,即使我不要求它的主要内容,如果未能解决你的问题,请参考以下文章

如何刷新实体框架核心 DBContext?

我需要帮助理解与实体框架上下文的第一种方法相关的 C# 语法 [重复]

实体框架,查询包含上下文更改的dbset,而不调用保存更改

实体框架长期数据上下文与短期数据上下文 [关闭]

继承托管对象

WPF 实体框架刷新一个上下文实体