Comment effectuer une jointure entre plusieurs tables dans LINQ lambda

89

J'essaye d'effectuer une jointure entre plusieurs tables dans LINQ. J'ai les classes suivantes:

Product {Id, ProdName, ProdQty}

Category {Id, CatName}

ProductCategory{ProdId, CatId} //association table

Et j'utilise le code suivant (où product, categoryet productcategorysont des instances des classes ci-dessus):

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

Avec ce code, j'obtiens un objet de la classe suivante:

QueryClass { productproductcategory, category}

Où la catégorie de produits est de type:

ProductProductCategoryClass {product, productcategory}

Je ne comprends pas où se trouve la "table" jointe, je m'attendais à une seule classe contenant toutes les propriétés des classes impliquées.

Mon objectif est de remplir un autre objet avec certaines propriétés résultant de la requête:

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

comment puis-je atteindre cet objectif?

CiccioMiami
la source
Je n'ai pas compris ... pourquoi m.ProdId = ??? au lieu de prodId = m.ProdId ?
Adriano Repetti
Parce que je ne sais pas à l'avance comment naviguer et obtenir ProdId
CiccioMiami

Réponses:

179

Pour les jointures, je préfère fortement la syntaxe de requête pour tous les détails qui sont heureusement cachés (dont les identifiants transparents impliqués dans les projections intermédiaires en cours de route qui sont apparents dans l'équivalent de syntaxe de point). Cependant, vous avez demandé à propos de Lambdas que je pense que vous avez tout ce dont vous avez besoin - il vous suffit de tout mettre ensemble.

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

Si vous en avez besoin, vous pouvez enregistrer la jointure dans une variable locale et la réutiliser plus tard, mais en l'absence d'autres détails au contraire, je ne vois aucune raison d'introduire la variable locale.

De plus, vous pouvez lancer le Selectdans le dernier lambda de la seconde Join(encore une fois, à condition qu'il n'y ait pas d'autres opérations qui dépendent des résultats de la jointure) ce qui donnerait:

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

... et faire une dernière tentative pour vous vendre sur la syntaxe de requête, cela ressemblerait à ceci:

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

Vos mains peuvent être liées sur la disponibilité de la syntaxe de requête. Je sais que certains magasins ont de tels mandats - souvent basés sur la notion que la syntaxe de requête est un peu plus limitée que la syntaxe à points. Il y a d'autres raisons, comme "pourquoi devrais-je apprendre une deuxième syntaxe si je peux tout faire et plus en syntaxe à points?" Comme le montre cette dernière partie - il y a des détails que la syntaxe de requête cache qui peuvent valoir la peine d'être embrassés avec l'amélioration de la lisibilité qu'elle apporte: toutes ces projections et identificateurs intermédiaires que vous devez préparer ne sont heureusement pas au centre. étape dans la version de syntaxe de requête - ce sont des peluches d'arrière-plan. De ma boîte à savon maintenant - de toute façon, merci pour la question. :)

devgeezer
la source
3
Merci votre solution est plus complète. Je suis d'accord que la syntaxe de requête dans certains cas est plus claire, mais vous avez bien deviné, on m'a demandé d'utiliser lambda. De plus, je dois faire cette jointure sur 6 tables, et la notation par points dans ce cas est plus soignée
CiccioMiami
@devgeezer Que faire si nous avons besoin d'une condition d'addition dans l' JOINinstruction? Comment fait-on cela? Par exemple, dans la join pc in productcategory on p.Id equals pc.ProdIdligne, nous devons ajouter and p.Id == 1.
Hélicoptère d'attaque Harambe le
Il semble suspect que vous le souhaitiez, p.Id == 1car il s'agit plus d'un filtre where que d'un critère de jointure. La façon dont vous feriez une jointure sur plus d'un critère est généralement d'utiliser un type anonyme: join pc in productcategory on new { Id = p.Id, Other = p.Other } equals new { Id = pc.ProdId, Other = pc.Other }. Cela fonctionne dans Linq-to-Objects, et je suppose que cela fonctionnera également avec les requêtes de base de données. Avec les bases de données, vous pouvez éviter les requêtes de jointure compliquées en définissant des clés étrangères comme il convient et en accédant aux données associées via la propriété associée.
devgeezer
Merci pour la solution propre.
Thomas.Benz
Dans votre exemple: dans la syntaxe dot, les ppc ppc.p sont des types anonymes, non? Dans la syntaxe de la requête, le p.id que vous utilisez dans la dernière sélection est toujours un objet produit, ai-je raison? Donc, avec la syntaxe de requête, c'est plus facile si vous joignez plusieurs tables pour effectuer des opérations dans le shema de retour final comme min minby?
CDrosos
12

Ce que vous avez vu est ce que vous obtenez - et c'est exactement ce que vous avez demandé, ici:

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

C'est une expression lambda renvoyant un type anonyme avec ces deux propriétés.

Dans vos produits classés, il vous suffit de passer par ces propriétés:

CategorizedProducts catProducts = query.Select(
      m => new { 
             ProdId = m.productproductcategory.product.Id, 
             CatId = m.category.CatId, 
             // other assignments 
           });
Jon Skeet
la source
Merci. Je comprends la discussion sur la classe anonyme mais ses propriétés contiennent uniquement les objets de classe qui répondent à la requête? Et que se passe-t-il après avoir effectué la jointure 2? productproductcategory.product n'est pas associé à la catégorie, non?
CiccioMiami
@CiccioMiami: Eh bien, les propriétés sont des références à des objets, oui. Ce que vous entendez par «non joint» n'est pas vraiment clair - quelles informations n'obtenez-vous pas de votre requête que vous souhaitez obtenir?
Jon Skeet
Avec la première jointure, j'obtiens la jointure entre les produits et la catégorie de produits. Avec le second, j'obtiens la jonction entre la catégorie de produit (produit joint) et la catégorie. Cela signifie que les informations sur la jointure multiple sont simplement contenues dans productproductcategory. Cela signifie que le produit (et la catégorie) sont simplement associés à la catégorie de produits.
CiccioMiami
1
@CiccioMiami: Désolé, je ne vous suis pas - mais si vous spécifiez la jointure, elle le fera. Avez-vous essayé d' utiliser le code dans ma réponse? Cela ne fait-il pas ce que vous voulez?
Jon Skeet
Désolé, je voulais accéder à votre code. L'affectation des CatId travaux très bien. Car ProdIdcela devrait être m.productproductcategory.product.IdOU m.productproductcategory.productcategory.ProdId. Les deux affectations diffèrent, la première est sur le produit (joint avec productcategory), la seconde est avec productcategoryjoint avec les deux productet category. Suivez-vous mon raisonnement?
CiccioMiami
4

jetez un œil à cet exemple de code de mon projet

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();
}

dans ce code, j'ai rejoint 3 tables et j'ai craché la condition de jointure à partir de la clause where

note: les classes Services sont juste déformées (encapsulent) les opérations de la base de données

Basheer AL-MOMANI
la source
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);



       }
saktiprasad swain
la source
1
Bien que cet extrait de code puisse résoudre la question, inclure une explication contribue vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
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}");
        }
Alex Martinez
la source
simple juste cela, mais c'est mieux avec lambda exp comme certains l'ont dit.
Alex Martinez
0

cela fait un moment mais ma réponse peut aider quelqu'un:

si vous avez déjà défini correctement la relation, vous pouvez utiliser ceci:

        var res = query.Products.Select(m => new
        {
            productID = product.Id,
            categoryID = m.ProductCategory.Select(s => s.Category.ID).ToList(),
        }).ToList();
iDeveloper
la source