实体框架属性中的虚拟关键字

问题描述 投票:0回答:2
public class Student
{
    public int StudentId;
    public string StudentName;
    public int CourseId;
    public virtual Course Courses { get; set; }
}

public class Course
{
    public int CourseId;
    public string CourseName;
    public string Description;
    public ICollection<Student> Students {get;set;}
    public ICollection<Lecture> Lectures { get; set; }
}

public class Lecture
{
    public int LectureId;
    public string LectureName;
    public int CourseId;
    public virtual Course Courses { get; set; }
}

这里使用的关键字

virtual
是什么?

有人告诉我虚拟是为了延迟加载,但我不明白为什么。 因为当我们这样做时

_context.Lecture.FirstOrDefault()

结果返回第一个

Lecture
,并且不包含属性
Course

要使用

Lecture
获得
Course
,我们必须使用:

_context.Lecture.Include("Courses").FirstOrDefault()

不使用

virtual
关键字,它已经是延迟加载了。

那我们为什么需要关键字?

c# .net entity-framework
2个回答
1
投票

通过声明它

virtual
,您允许 EF 用代理替换 value 属性以启用延迟加载。使用
Include()
告诉 EF 查询 eager 加载相关数据。

在 EF6 及更早版本中,默认启用延迟加载。对于 EF Core,它默认处于禁用状态。 (或者最早的版本不支持)

进行以下查询:

var lecture = _context.Lecture.Single(x => x.LectureId == lectureId);

加载一堂课。

如果省略

virtual
,则访问
lecture.Course
将执行以下两件事之一。如果 DbContext (_context) 尚未跟踪 Lecture.CourseId 所指向的课程实例,Lecture.Course 将返回 #null。如果 DbContext 已经在跟踪该实例,则 Lecture.Course 将返回该实例。因此,如果没有延迟加载,您可能会或可能不会获得参考,不要指望它在那里。

在同一场景中使用

virtual
和延迟加载,代理会检查 DbContext 是否已提供课程,如果是则返回。如果尚未加载,那么它将自动转到 DbContext(如果它仍在范围内)并尝试查询它。通过这种方式,如果您访问
lecture.Course
,如果数据库中有记录,您可以指望它会被返回。

将延迟加载视为安全网。如果依赖它,可能会带来巨大的性能成本,但有人可能会说,与数据不一致的运行时错误相比,性能损失是两害相权取其轻。这对于相关实体的集合来说非常明显。在上面的示例中,

ICollection<Student>
等也应标记为
virtual
,以确保它们可以延迟加载。如果没有它,您将返回当时可能跟踪的所有学生,这在运行时可能会出现非常不一致的数据状态。 例如,您有 2 门课程,课程#1 和课程#2。有 4 名学生,A、B、C 和 D。所有 4 名学生都注册到课程 #1,只有 A 和 B 注册到课程 B。如果我们通过删除

virtual

来忽略延迟加载,那么行为将会改变取决于我们首先加载的课程,如果我们碰巧在一种情况下急切加载而在第二种情况下忘记了......

using (var context = new MyAppDbContext())
{
    var course1 = context.Courses
        .Include(x => x.Students)
        .Single(x => x.CourseId == 1);
    var course2 = context.Courses
        .Single(x => x.CourseId == 2);

    var studentCount = course2.Students.Count();
}

免责声明:对于实体中的集合,您应该确保它们始终被初始化,以便它们准备就绪。这可以在构造函数或自动属性中完成:

public ICollection<Student> Students { get; set; } = new List<Student>();

在上面的示例中,studentCount 将返回为“2”,因为在加载课程 #1 时,学生 A 和 B 都是通过 
Include(x => x.Students)

加载的。这是一个非常明显的示例,依次加载两门课程,但这种情况当加载共享数据的多个记录(例如搜索结果等)时,很容易发生这种情况。它还受到 DbContext 存活时间的影响。此示例使用

using
块作为新的 DbContext 实例范围,范围仅限于 Web 请求,或者可以跟踪调用早期的相关实例。
现在反转场景:

using (var context = new MyAppDbContext()) { var course2 = context.Courses .Include(x => x.Students) .Single(x => x.CourseId == 2); var course1 = context.Courses .Single(x => x.CourseId == 1); var studentCount = course1.Students.Count(); }

在这种情况下,只有学生 A 和 B 被急切加载。虽然课程 1 实际上引用了 4 名学生,但此处的 StudentCount 将为与课程 1 关联的两名学生返回“2”,在加载课程 1 时 DbContext 正在跟踪这两名学生。您可能期望 4 或 0,因为您知道您没有急切地加载学生。由此产生的相关数据是不可靠的,您可能会或可能不会得到什么将视具体情况而定。

延迟加载会变得昂贵的地方是加载数据集时。假设我们加载 100 名学生的列表,在与这些学生合作时,我们访问 Student.Course。热切加载将生成 1 个 SQL 语句来加载 100 名学生及其相关课程。延迟加载最终将为学生执行 1 个查询,然后为每个学生执行 100 个查询来加载课程。 (即 SELECT * FROM Courses WHERE StudentId = 1; SELECT * FROM Courses WHERE StudentId = 2; ...)如果学生有多个延迟加载属性,那么每个延迟加载还需要 100 个查询。


0
投票
这个相似

,特别是这个答案和相关链接。另外这个网站将会很有用。

© www.soinside.com 2019 - 2024. All rights reserved.