c# - INNER 和 OUTER Join 的 LINQ 方法语法

标签 c# linq lambda linq-method-syntax

我有 3 个类(class)并尝试使用 LINQ methods执行 INNER JOIN和一个 LEFT JOIN .我能够分别执行每个操作,但在一起时运气不好,因为我什至无法弄清楚语法。

最终,我要编写的 SQL 将是:

SELECT *
FROM [Group] AS [g]
INNER JOIN [Section] AS [s] ON [s].[GroupId] = [g].[Id]
LEFT OUTER JOIN [Course] AS [c] ON [c].[SectionId] = [s].[Id]

public class Group {
    public int Id { get; set; }
    public int Name { get; set; }
    public bool IsActive { get; set; }
    public ICollection<Section> Sections { get; set; }
}

public class Section {
    public int Id { get; set; }
    public int Name { get; set; }
    public int GroupId { get; set; }
    public Group Group { get; set; }
    public bool IsActive { get; set; }
    public ICollection<Course> Courses { get; set; }
}

public class Course {
    public int Id { get; set; }
    public int UserId { get; set; }
    public int Name { get; set; }
    public int SectionId { get; set; }
    public bool IsActive { get; set; }
}

示例

我希望结果的类型为 Group .我成功执行了 LEFT JOINSection 之间和 Course , 但我有一个 IQueryable< 类型的对象一个> , which is not what I want, since组`.

var result = db.Section
               .GroupJoin(db.Course, 
                    s => s.Id,
                    c => c.SectionId,
                    (s, c) => new { s, c = c.DefaultIfEmpty() })
               .SelectMany(s => s.c.Select(c => new { s = s.s, c }));

我也试过这个,但返回 NULL因为这会执行 INNER JOIN在所有表上,并且用户没有输入任何 Courses .

var result = db.Groups
               .Where(g => g.IsActive)
               .Include(g => g.Sections)
               .Include(g => g.Sections.Select(s => s.Courses))
               .Where(g => g.Sections.Any(s => s.IsActive && s.Courses.Any(c => c.UserId == _userId && c.IsActive)))
               .ToList();

问题

如何执行 INNER和一个 LEFT JOIN调用数据库的次数最少,得到类型为 Group 的结果?

期望的结果

我想要 1 个类型为 Group 的对象, 但只有 Group有一个 Section .我还想返回 Courses用户具有特定的 Section或返回 NULL .

最佳答案

我认为如果不返回新的(匿名)对象而不是 Group(如 this answer 中所示),您的要求是不可能的。由于关系和实体缓存的工作方式,EF 不允许您在 Section 中获取经过过滤的 Course 集合,这意味着您不能为此任务使用导航属性.

首先,您想控制加载哪些相关实体,因此我建议通过将 SectionsCourses 集合属性标记为 virtual 在您的实体中(除非您已为应用程序中的所有实体启用延迟加载)因为我们不希望 EF 加载相关的 SectionsCourses 因为它无论如何都会为每个用户加载所有类(class)。

public class Group {
    public int Id { get; set; }
    public int Name { get; set; }
    public bool IsActive { get; set; }
    public virtual ICollection<Section> Sections { get; set; }
}

public class Section {
    public int Id { get; set; }
    public int Name { get; set; }
    public int GroupId { get; set; }
    public Group Group { get; set; }
    public bool IsActive { get; set; }
    public virtual ICollection<Course> Courses { get; set; }
}

在方法语法中,查询可能看起来像这样:

var results = db.Group
    .Where(g => g.IsActive)
    .GroupJoin(
        db.Section.Where(s => s.IsActive),
        g => g.Id,
        s => s.GroupId,
        (g, s) => new
        {
            Group = g,
            UserSections = s
                .GroupJoin(
                    db.Course.Where(c => c.IsActive && c.UserId == _userId).DefaultIfEmpty(),
                    ss => ss.Id,
                    cc => cc.SectionId,
                    (ss, cc) => new
                    {
                        Section = ss,
                        UserCourses = cc
                    }
                )
        })
    .ToList();

你会消费结果为:

foreach (var result in results)
{
    var group = result.Group;

    foreach (var userSection in result.UserSections)
    {
        var section = userSection.Section;

        var userCourses = userSection.UserCourses;

    }
}

现在,如果您不需要在数据库级别对组结果进行额外过滤,您也可以使用此 LINQ 查询采用 INNER JOIN 和 LEFT OUTER JOIN 方法并在内存中进行分组:

var results = db.Group
    .Where(g => g.IsActive)
    .Join(
        db.Section.Where(s => s.IsActive),
        g => g.Id,
        s => s.GroupId,
        (g, s) => new
        {
            Group = g,
            UserSection = new
            {
                Section = s,
                UserCourses = db.Course.Where(c => c.IsActive && c.UserId == _userId && c.SectionId == s.Id).DefaultIfEmpty()
            }
        })
    .ToList() // Data gets fetched from database at this point
    .GroupBy(x => x.Group) // In-memory grouping
    .Select(x => new
    {
        Group = x.Key,
        UserSections = x.Select(us => new
        {
            Section = us.UserSection,
            UserCourses = us.UserSection.UserCourses
        })
    });

请记住,无论何时您尝试访问 group.Sectionssection.Courses,您都会触发延迟加载,这将获取所有子部分或类(class),无论_userId

关于c# - INNER 和 OUTER Join 的 LINQ 方法语法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51410209/

相关文章:

c# - Code Contracts + SandcaSTLe——有什么方法可以自定义异常部分吗?

c# - Linq 选择动态属性列表到另一个对象?

c# - 什么是 linq "best practices"

带有 auto 关键字的 C++11 函数定义

recursion - 如何理解clojure的lazy-seq

c# - 如何检查动画师的某个动画状态是否正在运行?

c# - 如何清除() Entity Framework ICollection 中的所有元素?

c# - autogeneratecolumns=true 时如何实现 wrap 属性?

C# LINQ 和 XML 获取子节点

c# - 如何将 LambdaExpression 类型的对象传递给只接受 Expression<Func<T, retT>> 的方法?