LINQ 按同一列连接多个表

Posted

技术标签:

【中文标题】LINQ 按同一列连接多个表【英文标题】:LINQ join multiple tables by same column 【发布时间】:2021-10-29 13:25:28 【问题描述】:

我有一张表Texting,我需要加入另外两张表StudentStaff来搜索这两张表的信息.

学生字段:

    身份证 姓名 ...以及许多其他特定于学生的领域

员工字段:

    身份证 姓名 ...以及许多其他特定于员工的字段

发短信字段:

    身份证 PersonId // 包含学生 ID 或教职员工 ID PersonTypeId //表示PersonId是student还是staff类型(student = 1,staff = 2)

现在我需要编写一个 linq 查询来按学生或教职员工姓名搜索表 Texting,但我被困在 linq 上来实现这一点。

var query = (from t in texting
             join s in studentBo.GetListQuery()
             on t.PersonId equals s.Id
             join st in staffBo.GetListQuery()
             on t.PersonId equals st.Id
             where ...
             select t);

这将表连接在一起,但它不关心 PersonId 类型是什么,所以它是混合的。我如何指定以便它根据正确的 PersonTypeId 正确加入 PersonId?似乎不能在 on 子句或 where 子句上附加任何其他内容来实现这一点 = (.

【问题讨论】:

您只选择Texting 项目,因此您不需要连接。可以用from t in texting where studentBo.GetListQuery().Any(...)等,希望一切都是IQueryable 【参考方案1】:

因此,您有一个 name,并且您希望所有使用此名称引用 StudentTextings,以及使用此名称引用 Staff 成员的所有 Textings

我的建议是将学生短信与员工短信联系起来。你可以在一个大的 LINQ 语句中做到这一点,但这会让人很难理解。所以我会分两步做,然后在一个查询中连接它:

const int student = 1;
string name = "William Shakespeare";

var studentTextings = textings.Where(texting => texting.PersonTypeId == student)
    .Join(students.Where(student => student.Name == name),

    texting => texting.PersonId,    // from every Texting take the foreign key
    student => student.Id,          // from every Student take the primary key

    // parameter resultSelector:
    // from every texting with its matching student make one new:
    (texting, studentWithThisTexting) => new
    
        // Select the Texting properties that you plan to use
        Id = texting.Id,
        ...
    

换句话说:在所有短信中,只保留那些涉及学生的短信,这样您就知道外键是指学生表中的主键。从所有学生中只保留具有请求名称的学生。

加入所有剩余的短信和少数剩余的在主键和匹配外键上有此名称的学生。

为员工做类似的事情:

const int staff = 2;
var staffTextings = textings.Where(texting => texting.PersonTypeId == staff)
    .Join(staffMembers.Where(staffMember => staffMember.Name == name),

    texting => texting.PersonId,    // from every Texting take the foreign key
    staffMember => staffMember.Id,  // from every Staff member take the primary key

    // parameter resultSelector:
    (texting, staffMembers) => new
    
        // Select the Texting properties that you plan to use
        Id = texting.Id,
        ...
    

现在你要做的就是连接这两个。请注意:您只能 Concat 相似的项目,因此两个 Join 中的 resultSelector 应该选择完全相同类型的对象。

var textingsOfPersonsWithThisName = studentTextings.Concat(staffTextings);

还有改进的余地!

如果您仔细观察,您会发现短信表会被扫描两次。原因是你的数据库没有规范化。

有没有可能,给学生发短信会变成给员工发短信?如果没有,我的建议是制作两张表:StudentTextings 和 StaffTextings。除了查询会更快,因为您不必检查PersonType,这还有一个好处是,如果稍后您确定 StudentTexting 与 StaffTexting 不同,您可以更改表格而不会遇到问题。

如果您确实认为有时需要更改短信的类型,并且您不想通过创建新短信来做到这一点,那么您还应该有两个表格:一个带有 StudentTextings,一个带有 StaffTextings,这两个表都与短信具有一对一的关系。

所以学生与 StudentTextings 是一对多的,而 StudentTextings 与 Textings 是一对一的。 Staff 和 StaffTextings 类似。

所以学生 [4] 有 3 个学生短信,ID 为 [30]、[34]、[37]。这些 StudentTexting 中的每一个都有一个外键 StudentId,其值为 [4]。每个 StudentTexting 用外键引用自己的 Texting:[30] 指的是 texting [101],所以它有外键 101,等等。

现在,如果发短信 [101] 必须成为员工 [7] 的发短信,您必须删除引用 [101] 的 StudentTexting 并创建一个引用员工 [7] 和发短信 [101] 的新员工发短信]

顺便说一句,由于组合 [StudentId, TextingId] 将是唯一的,表 StudentTextings 可以使用这个组合作为主键。 StaffTextings 类似

【讨论】:

【参考方案2】:

你必须合并 Student 和 Staff 表,否则你的所有查询都会太复杂,因为你必须使用 Union

Person

Id
Name
PersonType

Texting

Id
PersonId

查询

var query = (from t in texting
             join p in person
             on t.PersonId equals p.Id
                  where ...
             select t);

PS 如果您仍然想要使用 2 个表而不是 1 个表进行查询,则必须发布真实代码。

【讨论】:

【参考方案3】:

您需要将这些作为两个单独的查询进行,投影到新类型,然后合并结果。乱七八糟,但方法如下。

首先让你的学生:

var textingStudents = (
    from s in students
    join t in texting on s.Id equals t.PersonId
    where t.PersonTypeId == 1
    select new  id = s.Id, personTypeId = 1, name = s.Name ).ToList();

现在以几乎完全相同的方式让您的员工:

var textingStaff = (
    from s in staff
    join t in texting on s.Id equals t.PersonId
    where t.PersonTypeId == 2
    select new  id = s.Id, personTypeId = 2, name = s.Name ).ToList();

现在您可以将两者合并:

var allTextingPeople = textingStudents.Union(textingStaff);

如果您需要其他属性,则将 then 添加到 select 语句中声明的匿名类型 - 请记住,该类型需要在 textingStudentstextingStaff 结果中具有相同的属性。或者,定义一个类并在两个查询中执行 select new MyUnionClass ...

编辑 使用您概述的当前方法,您可能会进入一个受伤的世界。如果您使用的是关系数据库(即 sql 服务器),您几乎可以肯定没有在 Texting 表上定义诸如外键之类的约束,这意味着您最终会遇到 ID 冲突,并且以后肯定会遇到错误.最好的方法可能是用一个表来表示StaffStudent(我们称它为Person,其中有一列定义人员的“类型” - 该列本身将是另一个表的外键链接,其中包含您的列表PersonTypes

【讨论】:

最好使用Concat 而不是Union。为什么不将它们组合在一个查询中? 同意,Concat 在这里可能会更好。您不能在此处执行单个查询,因为当前形式的数据模型不支持它 - Texting 表有一个 ID 列,但包含 StudentStaff 表的 ID - 它不是真正的一对一(一对一(one-OR-one))? 您作为 EF Core 开发人员从某种模型的角度思考。是普通的SQL,为什么不直接生成呢?

以上是关于LINQ 按同一列连接多个表的主要内容,如果未能解决你的问题,请参考以下文章

对同一个表中的多个列执行内连接

Linq 连接表并创建动态列错误

具有别名的同一列上的多个连接

LINQ to SQL:多个列上的多个连接。这可能吗?

Linq 按连接表中的项目计数分组

Linq连接表和选择列