具有INNER和OUTER连接的LINQ方法语法

时间:2018-07-18 20:08:00

标签: c# linq lambda linq-method-syntax

我有3个类,并尝试使用LINQ methods来执行INNER JOINLEFT 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< a> , which is not what I want, since Group`的对象。

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();

问题

如何以最少的数据库调用次数执行INNERLEFT JOIN并得到类型Group的结果?

所需结果

我希望有一个类型为Group的对象,但是只要Group有一个Section。我还想返回用户对特定Courses拥有的Section或返回NULL

3 个答案:

答案 0 :(得分:2)

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

首先,您想控制要加载的相关实体,因此我建议通过在您的容器中将SectionsCourses集合属性标记为virtual来启用延迟加载。实体(除非您已为应用程序中的所有实体启用延迟加载),因为我们不希望EF加载相关的SectionsCourses,因为EF仍然会为每个用户加载所有课程。

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时,都会触发延迟加载,该延迟加载将获取所有子节或课程,而与_userId无关。

答案 1 :(得分:1)

使用DefaultIfEmpty执行外部左连接

from g in db.group
join s in db.section on g.Id equals s.GroupId 
join c in db.course on c.SectionId equals s.Id into courseGroup
from cg in courseGroup.DefaultIfEmpty()
select new { g, s, c }; 

答案 2 :(得分:1)

您的SQL的类型不是[Group](类型组将是:从[...]中选择[Group]。*),无论如何,如果您想要这样,则其简单形式将是:

var result = db.Groups.Where( g => g.Sections.Any() );

但是,如果您确实要转换SQL,则:

var result = from g in db.Groups
             from s in g.Sections
             from c in s.Courses.DefaultIfEmpty()
             select new {...};

即使这样做也可以:

var result = from g in db.Groups
             select new {...};

提示:在一个设计好的具有关系的数据库中,很少需要使用join关键字。而是使用导航属性。