如何使用Linq执行“复杂”连接

时间:2011-08-01 13:33:04

标签: linq linq-to-sql join

我需要加入两个对象(表格)A和B.对于任何A,可以有零到多个B。查询需要每A返回一行。

我希望在加入之前订购B,以便能够在某个条件下从B中选择所需的行。说B有一个列类型。如果存在类型1那么那就是我需要的B,否则:必须选择类型2等。

现在我考虑一下,即使在T-sql中我也不确定如何做到这一点。 我想是这样的:

SELECT A.*
FROM   A LEFT JOIN (
  SELECT * FROM B AS B1 WHERE B1.Type = (SELECT TOP 1 B2.Type FROM B AS B2  
                                         WHERE B2.JoinID = B1.JoinID
                                         ORDER BY B2.Type )
) AS B ON B.JoinID = A.JoinID

[编辑]

在sgtz的回答中,我试图让它发挥作用。如果必须再做一步,因为我想订购的字段不存在。我在步骤1中添加了这个字段,在步骤2中我选择了键并在步骤3中加入了所有内容,但是我收到一个错误“join子句中某个表达式的类型不正确。类型推断失败了打电话给'GroupJoin'。“加入“加入adressen1 on new {b.TopRelatieID ...”

    var adressen1 = from a in db.Adres
                    select new
                    {
                        RelatieAdres = a,
                        Sortering = (int)(a.AdresType.Code == codeVestAdres ?
                                            1 : a.AdresType.Code == codePostAdres ? 
                                               2 : (100 + (int)a.AdresType.Code.ToCharArray()[0]))
                    };

    var adressen2 = from b in adressen1
                    group b by new { RelatieID = b.RelatieAdres.RelatieID } into p
                    let TopAdresType = p.Min(at => at.Sortering)
                    select new { TopRelatieID = p.Key.RelatieID, TopAdresType };

    var q = from k in db.Klants
            join b in adressen2 on k.RelatieID equals b.TopRelatieID into b_join
            from b in b_join.DefaultIfEmpty()
            join a in adressen1 on new { b.TopRelatieID, b.TopAdresType } equals new { a.RelatieAdres.RelatieID, a.Sortering } into a_join
            from a in a_join.DefaultIfEmpty()

2 个答案:

答案 0 :(得分:0)

为了有意义,你应该至少添加一些东西来查询结果,而不仅仅是A. *。否则,您将获得A的副本,其中某些行可能重复。如果我正确理解了这个问题,那么这个SQL查询应该可以工作:

SELECT DISTINCT A.*, B.Type
FROM A LEFT JOIN 
(SELECT TOP (1) JoinID, Type
FROM B
ORDER BY Type
GROUP BY JoinID, Type
) AS B ON A.JoinID = B.JoinID

转换为LINQ,它是(更新

(from a in As
join b in
(from b1 in Bs
orderby b1.Type
group b1 by b1.JoinID into B1
from b11 in B1
group b11 by b11.Type into B11
from b111 in B11
select new { b111.JoinID, b111.Type }).Take(1)
on a.JoinID equals b.JoinID into a_b
from ab in a_b.DefaultIfEmpty()            
select new { a_b.JoinID, /*all other a properties*/ a_b.Type }).Distinct()

LINQ可能无法正常工作,但您应该抓住这个想法。

答案 1 :(得分:0)

这是一个有效的例子。我做了两个阶段。

    [Test]
    public void Test333()
    {
        List<Order> O;
        var M = Prepare333Data(out O);

        var OTop = from o in O
                   group o by new {id=o.id, orderid=o.orderid}
                   into p
                   let topType = p.Min(tt => tt.type)
                   select new Order(p.Key.id, p.Key.orderid, topType);

        var ljoin = from m in M
                    join t in OTop on m.id equals t.id into ts
                    from u in ts.DefaultIfEmpty()
                    select new {u.id, u.orderid, u.type};
    }

    public class Manufacturer
    {
        public Manufacturer(int id, string name)
        {
            this.id = id;
            this.name = name;
        }

        public int id { get; set; }
        public string name { get; set; }
    }

    public class Order
    {
        public Order(int id, int orderid, int type)
        {
            this.orderid = orderid;
            this.id = id;
            this.type = type;
        }

        public int orderid { get; set; }
        public int id { get; set; }
        public int type { get; set; }
    }


    private List<Manufacturer> Prepare333Data(out List<Order> O)
    {
        var M = new List<Manufacturer>() {new Manufacturer(1, "Abc"), new Manufacturer(2, "Def")};
        O = new List<Order>()
                {
                    new Order(1, 1, 2),
                    new Order(1, 2, 2),
                    new Order(1, 2, 3),
                    new Order(2, 3, 1)
                    ,
                    new Order(2, 3, 1)
                    ,
                    new Order(2, 3, 2)
                };
        return M;
    }

对评论的回应:

你的“new {”会创建一个新的匿名类型。如果类型以相同的顺序声明并且它们具有相同的类型定义(即int匹配int,而不是int匹配short),则由差异进程创建的两个匿名类型被称为具有相同的签名。我没有在LINQ中广泛测试这种情况。

这就是为什么我使用真正的具体类,而不是JOIN部分中的anon类型。可能有一种方法可以用纯LINQ重做它,但我不知道它是什么还是。如果它发生在我身上,我会给你回复。

我建议现在也使用具体课程 即代替

 *new {*

进行连接时,请始终使用

 *new CLASSNAME(){prop1="abc",prop2="123"*

它有点长,但更安全......至少在我们弄清LINQ内部的内容之前更安全。