具有第一个或默认连接的 Linq 查询

Linq query with first or default join

我有以下数据模型:

public class Course
{
    public int CourseId { get; set; }
    public int StateId { get; set; }
}

public class CompletedCourse
{
    public int CompletedCourseId { get; set; }
    public int UserId { get; set; }
    public Course Course { get; set; }
    public string LicenseNumber { get; set; }
}

public class License
{
    public int LicenseId { get; set; }
    public int UserId { get; set; }
    public int StateId { get; set; }
    public string LicenseNumber { get; set; } 
}

我正在尝试为 CompletedCourses 想出一个 IQueryable,我想用 LicenseNumber 属性 填充 CompletedCourse.LicenseNumber FirstOrDefault() 从我的许可证中选择 table,其中 UserIdStateId 与已完成的课程记录匹配。

这是我的查询,但我认为这不会正确处理重复的许可证:

var entries =
    (from course in context.CompletedCourses
         join license in context.Licenses on course.UserId equals license.UserId
         where license.StateId == course.Course.StateId
         select course)
    .Include(x => x.Agent)
    .Include(x => x.Course.State);

这是可以在单个查询中完成的事情吗?提前致谢。

以下是您可以如何做到这一点:

var entries =
    (from course in context.CompletedCourses
     join license in context.Licenses
     on new { course.UserId, course.Course.StateId }
     equals new { license.UserId, license.StateId }
     into licenses
     let licenseNumber = licenses.Select(license => license.LicenseNumber).FirstOrDefault()
     select new { course, licenseNumber });

但请注意,使用这种类型的投影,您的查询中不能有 Includes(可以,但它们不会生效)。

我从上面得到的 EF 生成的查询是:

SELECT 
    [Extent1].[CompletedCourseId] AS [CompletedCourseId], 
    [Extent1].[UserId] AS [UserId], 
    [Extent1].[LicenseNumber] AS [LicenseNumber], 
    [Extent1].[Course_CourseId] AS [Course_CourseId], 
    (SELECT TOP (1) 
        [Extent2].[LicenseNumber] AS [LicenseNumber]
        FROM  [dbo].[Licenses] AS [Extent2]
        INNER JOIN [dbo].[Courses] AS [Extent3] ON [Extent3].[StateId] = [Extent2].[StateId]
        WHERE ([Extent1].[Course_CourseId] = [Extent3].[CourseId]) AND ([Extent1].[UserId] = [Extent2].[UserId])) AS [C1]
    FROM [dbo].[CompletedCourses] AS [Extent1]

可以注意到EF有效地忽略了join,所以通过简单的自然查询可以获得相同的结果:

var entries =
    (from course in db.CompletedCourses
     let licenseNumber =
        (from license in db.Licenses
         where license.UserId == course.UserId && license.StateId == course.Course.StateId
         select license.LicenseNumber).FirstOrDefault()
     select new { course, licenseNumber });

@IvanStoev 的回答对加入匿​​名类型非常有帮助,但最终我无法使用它,因为我需要包含。这是我使用的解决方案,它会导致两个数据库查询,而不是一个适合我的情况的查询。

var entries = context.CompletedCourses
    .Include(x => x.Agent)
    .Include(x => x.Course);
var courses = entries.ToList();
var courseIds = entries.Select(x => x.CompletedCourseId);
var licenses =
    (from course in entries
        join license in context.Licenses
        on new { course.AgentId, course.Course.StateId } 
        equals new { AgentId = license.UserId, license.StateId }
        where courseIds.Contains(course.CompletedCourseId)
        select license);
foreach (var course in courses)
{
    var license = agentLicenses.FirstOrDefault(x => x.UserId == course.AgentId && 
        x.StateId == course.Course.StateId);
    if (license != null)
    {
        course.LicenseNumber = license.LicenseNumber;
    }
}
return courses;