实体框架始终会包含上下文中的数据,即使我没有请求它。

23

我正在使用MVC.NET Web API、DB First的EF,并在我的上下文中关闭了懒加载。即使关闭了LazyLoading,EF仍然返回过多的数据。

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

为什么我不能让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

简而言之,我希望EF从不加载导航属性/集合中的数据,除非我直接使用.Include()。在序列化为JSON时,我只想要明确请求的内容。即使关闭了延迟加载,已经存在于上下文中的导航属性(通常是“循环引用”)也会被加载并返回。


1
关闭懒加载,那不是急加载吗? - Robert Harvey
2
只是一个小提醒。惰性加载意味着它只会在需要时加载所需的内容...关闭它与你所寻找的相反。 - Rick Petersen
1
据我所了解并测试过的 - 惰性加载意味着当您访问实体上的属性时,它会加载该集合。我关闭它是因为我不希望它加载除我要包含的内容以外的任何东西。我的问题是,即使关闭了惰性加载,它仍然包括已在上下文中加载的集合,因此用户有一个角色,而角色有多个用户作为导航属性,并且已经加载了数千个用户,即使我没有告诉EF包含它。 - Chris Putnam
不是这样的,懒加载将在您访问数据时加载它,而急加载会在创建选择命令时立即加载它。无论如何,您之所以获取这些数据,只是因为您正在访问它,如果您不触碰这些属性,EF 就不会从数据库中加载它们。您应该使用 SQL 分析器进行测试,以查看何时执行哪些查询。 - Felix
我跟@chris1234p有一样的问题。在我的情况下,我试图加载一系列评论,每个评论都有一个父级评论。不希望填充父级评论(但它们还是被填充了),似乎EF在我第一次接触这个表时就宣布了开放季节。对于其他尚未“在范围内”的关系(例如作者),我没有经历相同的问题。 - Tyler
显示剩余3条评论
5个回答

22

你所看到的行为被称为关系修复,您无法禁用它。

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

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

或者像 @STLRick 建议的一样,使用针对序列化专门优化的对象进行投影。


2
由于某些原因,当我尝试使用AsNoTracking时,在序列化期间会出现错误。 - Chris Putnam
@chris1234p:也许您可以提出一个新问题并描述这些错误。我关注的是您如何避免填充实体之间所有导航属性的问题。 - Slauma

2

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

var users = _db.Users.Select(x => new
{
    UserID = x.UserID,
    Title = x.Title,
    Email = x.Email,
    RoleID = x.RoleID
}).AsEnumerable();

2
您说得对,启用了懒加载后,您将获得导航属性,因为它们被序列化器“触摸”,从而导致它们被加载。如果您希望属性返回为空,则应关闭懒加载。话虽如此,似乎一旦实体通过其他查询加载到上下文中,它们就会被序列化器处理。因此,答案是告诉序列化器不要返回导航属性。我能找到的最好方法是使用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));

1
我不希望它加载除我指定的内容以外的任何东西。
看起来你需要使用显式加载。基本上,你可以像这样加载特定的实体:
context.Include("Roles")

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

如果需求的一部分是希望对象仍然附加到上下文中,因此可以进行更新等操作,则这比我以下的解决方案更好。请确保始终包含您需要的内容;如果这样做,我同意Serge的看法,这可能会让您更接近您想要的结果。 - Rick Petersen
1
你好Serge,这就是我正在做的事情,但似乎为了只包含我要求的内容,我需要使用一个全新的上下文。否则,如果数据已经在上下文中,即使我没有请求它,它也会被加载到返回的数据中。就像它试图通过给我一些我不需要的东西来“帮助”我一样。 - Chris Putnam

-3

第一步:打开懒加载。

第二步:如果你想筛选你检索和返回的内容,那么可以创建一个自定义的返回对象或其他方式。

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 }

或者类似这样。你将拥有一个最小的返回对象,你的对象图将非常简洁。


1
启用“懒加载”后,您将收到导航属性,因为序列化器会访问它们。 - user1843640

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接