So führen Sie eine Verknüpfung zwischen mehreren Tabellen in LINQ Lambda durch


88

Ich versuche, einen Join zwischen mehreren Tabellen in LINQ durchzuführen . Ich habe folgende Klassen:

Product {Id, ProdName, ProdQty}

Category {Id, CatName}

ProductCategory{ProdId, CatId} //association table

Und ich verwende den folgenden Code (wo product, categoryund productcategorysind Instanzen der oben genannten Klassen):

var query = product.Join(productcategory, p => p.Id, pc => pc.ProdID, (p, pc) => new {product = p, productcategory = pc})
                   .Join(category, ppc => ppc.productcategory.CatId, c => c.Id, (ppc, c) => new { productproductcategory = ppc, category = c});

Mit diesem Code erhalte ich ein Objekt aus folgender Klasse:

QueryClass { productproductcategory, category}

Wo die Produktkategorie vom Typ ist:

ProductProductCategoryClass {product, productcategory}

Ich verstehe nicht, wo sich die verknüpfte "Tabelle" befindet. Ich habe eine einzelne Klasse erwartet , die alle Eigenschaften der beteiligten Klassen enthält.

Mein Ziel ist es, ein anderes Objekt mit einigen Eigenschaften zu füllen, die sich aus der Abfrage ergeben:

CategorizedProducts catProducts = query.Select(m => new { m.ProdId = ???, m.CatId = ???, //other assignments });

Wie kann ich dieses Ziel erreichen?


Ich habe nicht verstanden ... warum m.ProdId = ??? anstelle von prodId = m.ProdId ?
Adriano Repetti

Weil ich nicht im Voraus weiß, wie man navigiert und ProdId
CiccioMiami erhält

Antworten:


178

Für Joins bevorzuge ich nachdrücklich die Abfragesyntax für alle Details, die glücklich verborgen sind (nicht zuletzt die transparenten Bezeichner, die mit den Zwischenprojektionen auf dem Weg verbunden sind, die im Punktsyntaxäquivalent erkennbar sind). Sie haben jedoch nach Lambdas gefragt, von denen ich denke, dass Sie alles haben, was Sie brauchen - Sie müssen nur alles zusammenfügen.

var categorizedProducts = product
    .Join(productcategory, p => p.Id, pc => pc.ProdId, (p, pc) => new { p, pc })
    .Join(category, ppc => ppc.pc.CatId, c => c.Id, (ppc, c) => new { ppc, c })
    .Select(m => new { 
        ProdId = m.ppc.p.Id, // or m.ppc.pc.ProdId
        CatId = m.c.CatId
        // other assignments
    });

Wenn nötig, können Sie den Join in einer lokalen Variablen speichern und später wiederverwenden. Da jedoch keine anderen Details vorliegen, sehe ich keinen Grund, die lokale Variable einzuführen.

Sie können das auch Selectin das letzte Lambda des zweiten werfen Join(vorausgesetzt, es gibt keine anderen Operationen, die von den Verknüpfungsergebnissen abhängen), die Folgendes ergeben würden:

var categorizedProducts = product
    .Join(productcategory, p => p.Id, pc => pc.ProdId, (p, pc) => new { p, pc })
    .Join(category, ppc => ppc.pc.CatId, c => c.Id, (ppc, c) => new {
        ProdId = ppc.p.Id, // or ppc.pc.ProdId
        CatId = c.CatId
        // other assignments
    });

... und wenn Sie einen letzten Versuch unternehmen, Ihnen die Abfragesyntax zu verkaufen, würde dies folgendermaßen aussehen:

var categorizedProducts =
    from p in product
    join pc in productcategory on p.Id equals pc.ProdId
    join c in category on pc.CatId equals c.Id
    select new {
        ProdId = p.Id, // or pc.ProdId
        CatId = c.CatId
        // other assignments
    };

Möglicherweise sind Ihnen die Hände gebunden, ob die Abfragesyntax verfügbar ist. Ich weiß, dass einige Geschäfte solche Mandate haben - oft basierend auf der Vorstellung, dass die Abfragesyntax etwas eingeschränkter ist als die Punktsyntax. Es gibt andere Gründe, wie "Warum sollte ich eine zweite Syntax lernen, wenn ich alles und mehr in Punktsyntax tun kann?" Wie dieser letzte Teil zeigt, gibt es Details, die die Abfragesyntax verbirgt und die es wert sind, mit der damit verbundenen Verbesserung der Lesbarkeit in Betracht gezogen zu werden: Alle Zwischenprojektionen und Bezeichner, die Sie erstellen müssen, sind glücklicherweise nicht frontal und zentriert. Stufe in der Abfragesyntaxversion - sie sind Hintergrundflusen. Jetzt aus meiner Seifenkiste - jedenfalls danke für die Frage. :) :)


3
Vielen Dank, dass Ihre Lösung vollständiger ist. Ich bin damit einverstanden, dass die Abfragesyntax in einigen Fällen klarer ist, aber Sie haben richtig geraten, ich wurde gebeten, Lambda zu verwenden. Außerdem muss ich diese Verknüpfungen über 6 Tabellen machen, und die Punktnotation ist in diesem Fall ordentlicher
CiccioMiami

@devgeezer Was ist, wenn wir eine Zusatzbedingung in der JOINAnweisung benötigen ? Wie machen wir das? Zum Beispiel join pc in productcategory on p.Id equals pc.ProdIdmüssen wir in der Zeile hinzufügen and p.Id == 1.
Harambe Attack Helicopter

Es scheint verdächtig, dass Sie wollen, p.Id == 1da dies eher ein Where-Filter als ein Join-Kriterium ist. Die Art und Weise, wie Sie einen Join nach mehr als einem Kriterium durchführen, besteht im Allgemeinen darin, einen anonymen Typ zu verwenden : join pc in productcategory on new { Id = p.Id, Other = p.Other } equals new { Id = pc.ProdId, Other = pc.Other }. Dies funktioniert in Linq-to-Objects, und ich gehe davon aus, dass dies auch bei Datenbankabfragen funktioniert. Mit Datenbanken können Sie möglicherweise auf komplizierte Join-Abfragen verzichten, indem Sie Fremdschlüssel als geeignet definieren und über die zugehörige Eigenschaft auf verwandte Daten zugreifen.
Devgeezer

Vielen Dank für die saubere Lösung.
Thomas.Benz

In Ihrem Beispiel: In der Punktsyntax sind die ppc ppc.p anonyme Typen, oder? In der Abfragesyntax ist die in der letzten Auswahl verwendete p.id immer noch ein Produktobjekt. Stimmt das? Die Abfragesyntax ist also einfacher, wenn Sie mehrere Tabellen verbinden, um Operationen im endgültigen Rückgabeschema wie min minby auszuführen.
CDrosos

12

Was Sie gesehen haben, ist das, was Sie bekommen - und genau darum haben Sie hier gebeten:

(ppc, c) => new { productproductcategory = ppc, category = c}

Dies ist ein Lambda-Ausdruck, der einen anonymen Typ mit diesen beiden Eigenschaften zurückgibt.

In Ihren CategorizedProducts müssen Sie nur die folgenden Eigenschaften verwenden:

CategorizedProducts catProducts = query.Select(
      m => new { 
             ProdId = m.productproductcategory.product.Id, 
             CatId = m.category.CatId, 
             // other assignments 
           });

Vielen Dank. Ich verstehe die Diskussion über die anonyme Klasse, aber ihre Eigenschaften enthalten nur die Klassenobjekte, die die Abfrage erfüllen. Und was passiert nach dem 2 Join? productproductcategory.product ist nicht mit der Kategorie verbunden, oder?
Ciccio Miami

@CiccioMiami: Nun, die Eigenschaften sind Verweise auf Objekte, ja. Es ist nicht wirklich klar, was Sie unter "nicht verbunden" verstehen - welche Informationen erhalten Sie nicht aus Ihrer Anfrage, die Sie erhalten möchten?
Jon Skeet

Beim ersten Join erhalte ich den Join zwischen Produkten und Produktkategorie. Mit der zweiten bekomme ich die Verknüpfung zwischen Produktkategorie (verbundenes Produkt) und Kategorie. Das bedeutet, dass die Informationen über die Mehrfachverknüpfung nur in der Produktproduktkategorie enthalten sind. Dies bedeutet, dass Produkt (und Kategorie) nur mit der Produktkategorie verknüpft werden.
Ciccio Miami

1
@CiccioMiami: Entschuldigung, ich folge Ihnen nicht - aber wenn Sie den Join angeben, wird es dies tun. Haben Sie versucht , den Code in meiner Antwort zu verwenden? Tut es nicht was du willst?
Jon Skeet

Entschuldigung, ich wollte zu Ihrem Code gelangen. Die Zuordnung CatId funktioniert einwandfrei. Denn ProdIdes sollte m.productproductcategory.product.IdODER sein m.productproductcategory.productcategory.ProdId. Die beiden Zuordnungen unterscheiden sich, die erste bezieht sich auf das Produkt (verbunden mit productcategory), die zweite auf productcategoryverbunden mit beiden productund category. Folgen Sie meiner Argumentation?
Ciccio Miami

4

Schauen Sie sich diesen Beispielcode aus meinem Projekt an

public static IList<Letter> GetDepartmentLettersLinq(int departmentId)
{
    IEnumerable<Letter> allDepartmentLetters =
        from allLetter in LetterService.GetAllLetters()
        join allUser in UserService.GetAllUsers() on allLetter.EmployeeID equals allUser.ID into usersGroup
        from user in usersGroup.DefaultIfEmpty()// here is the tricky part
        join allDepartment in DepartmentService.GetAllDepartments() on user.DepartmentID equals allDepartment.ID
        where allDepartment.ID == departmentId
        select allLetter;

    return allDepartmentLetters.ToArray();
}

In diesem Code habe ich 3 Tabellen verbunden und die Join-Bedingung aus der where-Klausel ausgespuckt

Hinweis: Die Services-Klassen werden nur die Datenbankoperationen verzerrt (gekapselt)


2
 public ActionResult Index()
    {
        List<CustomerOrder_Result> obj = new List<CustomerOrder_Result>();

       var  orderlist = (from a in db.OrderMasters
                         join b in db.Customers on a.CustomerId equals b.Id
                         join c in db.CustomerAddresses on b.Id equals c.CustomerId
                         where a.Status == "Pending"
                         select new
                         {
                             Customername = b.Customername,
                             Phone = b.Phone,
                             OrderId = a.OrderId,
                             OrderDate = a.OrderDate,
                             NoOfItems = a.NoOfItems,
                             Order_amt = a.Order_amt,
                             dis_amt = a.Dis_amt,
                             net_amt = a.Net_amt,
                             status=a.Status,  
                             address = c.address,
                             City = c.City,
                             State = c.State,
                             Pin = c.Pin

                         }) ;
       foreach (var item in orderlist)
       {

           CustomerOrder_Result clr = new CustomerOrder_Result();
           clr.Customername=item.Customername;
           clr.Phone = item.Phone;
           clr.OrderId = item.OrderId;
           clr.OrderDate = item.OrderDate;
           clr.NoOfItems = item.NoOfItems;
           clr.Order_amt = item.Order_amt;
           clr.net_amt = item.net_amt;
           clr.address = item.address;
           clr.City = item.City;
           clr.State = item.State;
           clr.Pin = item.Pin;
           clr.status = item.status;

           obj.Add(clr);



       }

1
Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich dabei, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen.
Dr. Rob Lang

0
var query = from a in d.tbl_Usuarios
                    from b in d.tblComidaPreferidas
                    from c in d.tblLugarNacimientoes
                    select new
                    {
                        _nombre = a.Nombre,
                        _comida = b.ComidaPreferida,
                        _lNacimiento = c.Ciudad
                    };
        foreach (var i in query)
        {
            Console.WriteLine($"{i._nombre } le gusta {i._comida} y nació en {i._lNacimiento}");
        }

Einfach nur das, aber es ist besser mit Lambda Exp, wie einige Leute sagten.
Alex Martinez

0

Es ist eine Weile her, aber meine Antwort kann jemandem helfen:

Wenn Sie die Beziehung bereits richtig definiert haben, können Sie Folgendes verwenden:

        var res = query.Products.Select(m => new
        {
            productID = product.Id,
            categoryID = m.ProductCategory.Select(s => s.Category.ID).ToList(),
        }).ToList();
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.