开发者

How to perform "complex" join using Linq

I need to join two objects (tables) A and B. For any A there can be zero to many B's. The query needs the return one row per A.

The B's I want to order before the join to be able to select the needed row from B's following a certain condition. Say B has a column Type. If there is a Type 1 then that's the B I need, if not: Type 2 must be selected, etc.

Now I think about it, I am not sure how I would to this even in T-sql. I think something like this:

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

[edit]

With the answer of sgtz I've tried to make it work. If have to make an additional step because the field I want to order by is not present. I add this field in step 1, in step 2 I make a selection of the keys and join everything in step 3, but there I receive an error "The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'GroupJoin'." on join "join a in 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()


Here's a worked example. I did it two stages.

    [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;
    }

response to comments:

your "new {" creates a new anonymous type. Two anonymous types created by difference processes are said to have the same signature if types are declared in the same order and they have the same type definition (i.e. int matches int, not int matches short). I haven't tested this scenario extensively in LINQ.

That's why I worked with real concrete classes, and not anon types within the JOIN portion. There's probably a way to rework it with pure LINQ, but I don't know what that is yet. I'll post you a response if it occurs to me okay.

I'd suggest using concrete classes too for now.
i.e. instead of

 *new {*

when doing joins, always use

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

It's a little bit longer, but safer... safer at least until we work out what is going on inside the LINQ internals.


To be meaningful, you should add at least something to query result, not only A.*. Otherwise you'll have a copy of A with some rows possibly duplicated. If I understood the question correctly, this SQL query should work:

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

Translated to LINQ, it is (UPDATED)

(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 may not work 100% correct, but you should grab the idea.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜