关于c#:Linq to SQL使用Lambda语法左外连接并在2列上进行连接(复合连接键)

Linq to SQL left outer join using Lambda syntax and joining on 2 columns (composite join key)

我正在尝试使用Linq to SQL作为Lambda表达式在2列上进行内部联接。 普通查询如下所示。

1
2
3
4
SELECT * FROM participants
LEFT OUTER JOIN prereg_participants ON prereg_participants.barcode = participants.barcode
AND participants.event_id = prereg_participants.event_id
WHERE (participants.event_id = 123)

我成功地在左列上使用以下代码进行了联接。

1
2
3
4
5
6
7
8
9
10
var dnrs = context.participants.GroupJoin(
    context.prereg_participants,
    x => x.barcode,
    y => y.barcode,
    (x, y) => new { deelnr = x, vi = y })
    .SelectMany(
    x => x.vi.DefaultIfEmpty(),
    (x, y) => new { deelnr = x, vi = y })
    .Where(x => x.deelnr.deelnr.event_id == 123)
    .ToList();

问题是使用上述Lambda时,我得到了太多结果,因为它缺少AND participants.event_id = prereg_participants.event_id部分。 但是,无论我怎么努力,我都没有得到正确数量的参与者。

我查看了以下现有问题,但是在编写正确的lambda时没有一个解决了我的问题。 而且大多数解决方案都是lambda格式的nog或不是多列上的Left外部联接。

如何在LINQ中在单个联接中的多个字段上进行联接

LINQ to SQL-具有多个联接条件的左外部联接

通过Lambda表达式使用两个以上的列进行分组

而其中大部分来自此Google搜索


我可以在可在Linq2Sql和Entity Framework中使用的复合外键对barcode, event_id上获得此LEFT OUTER JOIN,并根据此查询语法示例转换为lambda语法。

这是通过创建匿名投影来实现的,该投影用于连接条件的左侧和右侧匹配:

1
2
3
4
5
var dnrs = context.participants.GroupJoin(
    context.prereg_participants,
    x => new { JoinCol1 = x.barcode, JoinCol2 = x.event_id }, // Left table join key
    y => new { JoinCol1 = y.barcode, JoinCol2 = y.event_id }, // Right table join key
    ...

笔记

这种方法依赖于赋予相同匿名类的自动等同性,即:

Because the Equals and GetHashCode methods on anonymous types are defined in terms of the Equals and GetHashCode methods of the properties, two instances of the same anonymous type are equal only if all their properties are equal.

因此,对于连接键的两个投影必须是相同的类型才能是equal,编译器需要在后台将它们视为相同的匿名类,即:

  • 两个匿名投影中的连接列数必须相同
  • 字段类型必须是兼容的相同类型
  • 如果字段名称不同,那么您将需要为它们加上别名(我使用了JoinColx)

我在这里在GitHub上放置了一个示例应用程序。

可悲的是,表达式树中的值元组尚不支持,因此您需要在投影中坚持使用匿名类型。


查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        var owners =
            from person in People
            join pet in Pets
            on new
            {
                person.Id,
                person.Age,
            }
            equals new
            {
                pet.Id,
                Age = pet.Age * 2, // owner is twice age of pet
            }
            into pets
            from pet in pets.DefaultIfEmpty()
            select new PetOwner
            {
                Person = person,
                Pet = pet,
            };

Lambda:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        var owners = People.GroupJoin(
            Pets,
            person => new { person.Id, person.Age },
            pet => new { pet.Id, Age = pet.Age * 2 },
            (person, pet) => new
            {
                Person = person,
                Pets = pet,
            }).SelectMany(
            pet => pet.Pets.DefaultIfEmpty(),
            (people, pet) => new
            {
                people.Person,
                Pet = pet,
            });

查看代码,或克隆我的git repo,然后播放!


您可以通过使用匿名类型来做到这一点。

例:

1
2
3
4
var result = from a in context.participants
             join b context.prereg_participants on new { X = a.barcode, Y = a.event_id } equals new { X = b.barcode, Y = b.event_id } into A
             from b in A.DefaultIfEmpty()
             where a.event_id = 123