INNER 和 OUTER Join 的 LINQ 方法语法
LINQ Method Syntax with INNER and OUTER Join
我有 3 个 类 并尝试使用 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
。我在 Section
和 Course
之间成功执行了 LEFT JOIN
,但是我有一个 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 }));
我也试过这个,但是 returns 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
。我还想 return 用户对特定 Section
或 return NULL
.
的 Courses
使用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 };
你的 SQL 的类型不是 [Group] (Type group would be: select [Group].* from ...),不管怎样,如果你想要这样,那么简单的形式是:
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 关键字。而是使用导航属性。
我认为如果不返回新的(匿名)对象而不是 Group
(如 所示),您的要求是不可能的。由于关系和实体缓存的工作方式,EF 不允许您在 Section
中获取过滤的 Course
集合,这意味着您不能为此任务使用导航属性。
首先,您想控制加载哪些相关实体,因此我建议通过将 Sections
和 Courses
集合属性标记为 virtual
来启用延迟加载在您的实体中(除非您已为应用程序中的所有实体启用延迟加载),因为我们不希望 EF 加载相关的 Sections
和 Courses
,因为它无论如何都会为每个用户加载所有课程。
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.Sections
或 section.Courses
,您都会触发延迟加载,这将获取所有子部分或课程,而不管 _userId
。
我有 3 个 类 并尝试使用 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
。我在 Section
和 Course
之间成功执行了 LEFT JOIN
,但是我有一个 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 }));
我也试过这个,但是 returns 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
。我还想 return 用户对特定 Section
或 return NULL
.
Courses
使用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 };
你的 SQL 的类型不是 [Group] (Type group would be: select [Group].* from ...),不管怎样,如果你想要这样,那么简单的形式是:
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 关键字。而是使用导航属性。
我认为如果不返回新的(匿名)对象而不是 Group
(如 Section
中获取过滤的 Course
集合,这意味着您不能为此任务使用导航属性。
首先,您想控制加载哪些相关实体,因此我建议通过将 Sections
和 Courses
集合属性标记为 virtual
来启用延迟加载在您的实体中(除非您已为应用程序中的所有实体启用延迟加载),因为我们不希望 EF 加载相关的 Sections
和 Courses
,因为它无论如何都会为每个用户加载所有课程。
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.Sections
或 section.Courses
,您都会触发延迟加载,这将获取所有子部分或课程,而不管 _userId
。