代码之家  ›  专栏  ›  技术社区  ›  RoLYroLLs

带内外连接的linq方法语法

  •  3
  • RoLYroLLs  · 技术社区  · 8 年前

    我有三节课,试着用 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 ,但是我有一个类型的对象 IQueryable< a> , 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 因为它执行 内部连接 在所有表中,用户没有输入任何 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 以及 左连接 调用数据库的次数最少,并获得类型为 集团 是吗?

    期望结果

    我想要一个类型的对象 集团 ,但只要 集团 有一个 剖面图 是的。我还想把 课程 用户有 剖面图 或返回 无效的 是的。

    3 回复  |  直到 8 年前
        1
  •  2
  •   huysentruitw Marc Gravell    8 年前

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

    首先,您希望能够控制加载哪些相关实体,因此我建议通过标记 Sections Courses 集合属性为 virtual 在您的实体中(除非您为应用程序中的所有实体启用了延迟加载),因为我们不希望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查询来使用内部连接和左外部连接方法,并在内存中进行分组:

    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 是的。

        2
  •  2
  •   Cam Bruce    8 年前

    使用 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 }; 
    
        3
  •  1
  •   Cetin Basoz    8 年前

    SQL的类型不是[组](类型组应该是:选择[组].*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关键字。而是使用导航属性。