Entity Framework 6 - 使用导航属性从 0:M 关系加载数据

Entity Framework 6 - loading data from a 0:M relationship using navigational properties

这些天我越来越少使用连接,并尽可能依赖导航属性。

这是一个非常基本的架构:

用户

Id              int
Surname         string
FirstName       string

注册人数

Id              int
UserId          int (foreign key for Users.Id)
EnrolmentName   string
StartDate       datetime
EndDate         datetime

一个用户在 Enrolments table 中可能有 0 个、1 个或多个与其相关的注册。

现在在查询中,我想 select 所有用户行,以及他们第一次注册的 EnrolmentName 列。我喜欢尽可能精简我的查询,并且只 select 我需要从数据库中获取的内容。如果没有必要,我不喜欢返回整个实体。

这是我的查询(我 select 将相关数据直接放入视图模型)。

IList<UserVm> rows = db.Users
    .Select(
        x => new UserVm
        {
            Id = x.Id,
            Surname = x.Surname,
            FirstName = x.FirstName,
            FirstEnrolmentName = x.Enrolments.OrderBy(o => o.StartDate).FirstOrDefault().EnrolmentName
        }
    )
    .ToList();

我遇到的问题是它可以工作,但我认为当我遇到没有注册的用户时它应该会失败。我希望以下行会抱怨无法在空对象上找到 EnrolmentName 列。

FirstEnrolmentName = x.Enrolments.OrderBy(o => o.StartDate).FirstOrDefault().EnrolmentName

实际发生的是,它会将 EnrolmentName 列保留为 NULL,因为该用户没有 Enrolment 记录。

我想知道:

  1. 为什么这个查询有效,并且不会对 0 Enrolments.

  2. 的学生造成错误
  3. 是否有更简洁的查询编写方式,以便它仍然只有 1 次访问数据库,并且仍然只有 selects 所需的列子集,而不是所有列.

你说得对,你应该 select 只使用你实际计划使用的属性,从而将尽可能少的数据传输到你的本地进程。

LINQ 知道两种语句:构成查询的语句和将执行查询的语句。作曲家是 return IQueryable<...>(或 IEnumerable<...>)的 LINQ 方法,执行者是 return 和 IQueryable,但 TResult。例如 ToListFirstOrDefaultAnyMax、...

你应该始终确保执行器是你语句的最后一个,除非你绝对确定执行器后面的语句不会限制数据量。

db.Users.Select(user => new UserVm
{
    ...
    FirstEnrolMentName = user.Enrolments
        .OrderBy(enrolment => enrolment.StartDate)
        .FirstOrDefault()
        .EnrolmentName,
});

我想知道如果您有一个没有任何 Enrolments 的用户,这是否也有效。我的第一个猜测是 FirstOrDefault 会 return 为空,因此当你想获得 EnrolmentName

时你会有一个 ArgumentNullException

另一个问题是您首先 select 一个完整的 Enrolment,然后您丢弃了除名称之外的所有注册属性。最好只 select 您计划使用的属性,然后使用 FirstOrDefault:

var result = db.Users.Select(user => new UserVm
{
    ...
    FirstEnrolMentName = user.Enrolments
        .OrderBy(enrolment => enrolment.StartDate)
        .Select(enrolment => enrolment.EnrolmentName)
        .FirstOrDefault(),
});