【问题标题】:Entity Framework 6 - loading data from a 0:M relationship using navigational propertiesEntity Framework 6 - 使用导航属性从 0:M 关系加载数据
【发布时间】:2018-10-30 19:24:00
【问题描述】:

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

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

用户

Id              int
Surname         string
FirstName       string

注册

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

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

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

这是我的查询(我将相关数据直接选择到视图模型中)。

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

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

我想知道:

  1. 为什么此查询有效且不会导致 0 Enrolments 的学生出错。

  2. 是否有一种更简洁的方式来编写查询,以便它仍然只命中数据库 1 次,并且仍然只选择所需的列子集,而不是全部。

【问题讨论】:

    标签: entity-framework linq-to-sql entity-framework-6 linq-to-entities


    【解决方案1】:

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

    LINQ 知道两种语句:构成查询的语句和将执行查询的语句。作曲家是返回IQueryable&lt;...&gt;(或IEnumerable&lt;...&gt;)的LINQ方法,执行者是不返回IQueryable,而是返回TResult的函数。例如ToListFirstOrDefaultAnyMax、...

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

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

    我想知道如果您的用户没有任何Enrolments,这是否也可以工作。我的第一个猜测是 FirstOrDefault 会返回 null,因此当你想获得 EnrolmentName 时你会有一个 ArgumentNullException

    另一个问题是你先选择了一个完整的Enrolment,之后你就扔掉了除了名字之外的所有Enrollment属性。最好只选择你打算使用的属性,然后使用FirstOrDefault

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

    【讨论】:

    • 是的,我认为第二个代码 sn-p 是有道理的。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-01
    • 2015-03-29
    • 1970-01-01
    相关资源
    最近更新 更多