Jak wykonać łączenie między wieloma tabelami w LINQ lambda


91

Próbuję wykonać sprzężenie między wieloma tabelami w LINQ. Mam następujące zajęcia:

Product {Id, ProdName, ProdQty}

Category {Id, CatName}

ProductCategory{ProdId, CatId} //association table

I użyć kodu następujące (gdzie product, categoryi productcategorysą przypadki powyższych zajęć):

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

Za pomocą tego kodu otrzymuję obiekt z następującej klasy:

QueryClass { productproductcategory, category}

W przypadku gdy kategoria produktów jest typu:

ProductProductCategoryClass {product, productcategory}

Nie rozumiem, gdzie jest połączona „tabela”, spodziewałem się pojedynczej klasy zawierającej wszystkie właściwości z zaangażowanych klas.

Moim celem jest wypełnienie innego obiektu niektórymi właściwościami wynikającymi z zapytania:

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

jak mogę osiągnąć ten cel?


Nie rozumiem ... dlaczego m.ProdId = ??? zamiast prodId = m.ProdId ?
Adriano Repetti

Bo nie wiem z góry jak się poruszać i dostać ProdId
CiccioMiami

Odpowiedzi:


181

W przypadku złączeń zdecydowanie wolę składnię zapytania dla wszystkich szczegółów, które są szczęśliwie ukryte (z których nie najmniejszym są przezroczyste identyfikatory związane z rzutami pośrednimi po drodze, które są widoczne w odpowiedniku składni kropkowej). Jednak pytałeś o Lambdy, które myślę, że masz wszystko, czego potrzebujesz - wystarczy to wszystko poskładać.

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

Jeśli zajdzie taka potrzeba, możesz zapisać sprzężenie w zmiennej lokalnej i użyć jej później, jednak z powodu braku innych szczegółów, nie widzę powodu, aby wprowadzać zmienną lokalną.

Możesz również wrzucić the Selectdo ostatniej lambda sekundy Join(ponownie, pod warunkiem, że nie ma innych operacji zależnych od wyników łączenia), co dałoby:

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

... i podejmując ostatnią próbę sprzedania Cię na podstawie składni zapytania, wyglądałoby to tak:

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

Twoje ręce mogą być związane, czy składnia zapytania jest dostępna. Wiem, że niektóre sklepy mają takie uprawnienia - często oparte na założeniu, że składnia zapytań jest nieco bardziej ograniczona niż składnia kropkowa. Istnieją inne powody, takie jak „po co mam się uczyć drugiej składni, skoro mogę zrobić wszystko, a nawet więcej, używając składni kropkowej?” Jak pokazuje ta ostatnia część - istnieją szczegóły, które ukrywa składnia zapytań, które mogą sprawić, że warto ją uwzględnić dzięki poprawie czytelności, jaką przynosi: wszystkie te pośrednie projekcje i identyfikatory, które musisz ugotować, na szczęście nie są z przodu i na środku. etap w wersji składni zapytania - są one puszystym tłem. Teraz z mojej mydelniczki - w każdym razie dzięki za pytanie. :)


3
Dzięki temu Twoje rozwiązanie jest pełniejsze. Zgadzam się, że składnia zapytania w niektórych przypadkach jest bardziej przejrzysta, ale dobrze zgadłeś, zostałem poproszony o użycie lambda. Poza tym muszę sprawić, żeby to łączyło ponad 6 tabel, a notacja z kropką w tym przypadku jest bardziej zgrabna
CiccioMiami

@devgeezer A jeśli potrzebujemy dodatkowego warunku w JOINwyciągu? Jak to zrobimy? Na przykład w join pc in productcategory on p.Id equals pc.ProdIdlinii musimy dodać and p.Id == 1.
Helikopter szturmowy Harambe

Wydaje się podejrzane, że chcesz, p.Id == 1ponieważ jest to bardziej kryterium gdzie-filtru niż jest to kryterium łączenia. Sposób, w jaki chcesz zrobić sprzężenia na więcej niż jedno kryterium ogólnie jest użycie typu anonimowego: join pc in productcategory on new { Id = p.Id, Other = p.Other } equals new { Id = pc.ProdId, Other = pc.Other }. Działa to w Linq-to-Objects i przypuszczam, że to samo zadziała również z zapytaniami do bazy danych. W przypadku baz danych można zrezygnować ze skomplikowanych zapytań łączących, definiując odpowiednie klucze obce i uzyskując dostęp do powiązanych danych za pośrednictwem powiązanej właściwości.
devgeezer

Dziękuję za czyste rozwiązanie.
Thomas.Benz

W twoim przykładzie: w składni z kropką ppc ppc.p są typami anonimowymi, prawda? W składni zapytania p.id, którego użyłeś w ostatniej selekcji, nadal jest obiektem produktu, prawda? Więc składnia zapytania jest łatwiejsza, jeśli łączysz wiele tabel, aby wykonywać operacje w ostatecznym zwracającym shema, takim jak min minby?
CDrosos

12

To, co widziałeś, jest tym, co otrzymujesz - i dokładnie o to prosiłeś, tutaj:

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

To wyrażenie lambda zwracające anonimowy typ z tymi dwiema właściwościami.

W swoich skategoryzowanych produktach wystarczy przejść przez te właściwości:

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

Dzięki. Rozumiem dyskusję na temat klasy anonimowej, ale jej właściwości zawierają tylko obiekty klasy, które spełniają zapytanie? A co się stanie po wykonaniu połączenia 2? productproductcategory.product nie jest połączony z kategorią, prawda?
CiccioMiami

@CiccioMiami: Cóż, właściwości są odniesieniami do obiektów, tak. Nie jest do końca jasne, co masz na myśli, mówiąc „nie dołączono” - jakich informacji nie otrzymujesz z zapytania, które chcesz uzyskać?
Jon Skeet

Przy pierwszym połączeniu otrzymuję połączenie między produktami i kategorią produktów. W drugim otrzymuję połączenie między kategorią produktu (połączonym produktem) a kategorią. Oznacza to, że informacje o łączeniu wielokrotnym są po prostu zawarte w kategorii produktprodukt. Oznacza to, że produkt (i kategoria) jest właśnie połączony z kategorią produktu.
CiccioMiami

1
@CiccioMiami: Przepraszam, nie śledzę cię - ale jeśli określisz łączenie, zrobi to. Czy próbowałeś użyć kodu w mojej odpowiedzi? Czy nie robi tego, czego chcesz?
Jon Skeet

Przepraszam, chciałem dostać się do twojego kodu. Przypisanie CatId prac w porządku. Bo ProdIdto powinno być m.productproductcategory.product.IdLUB m.productproductcategory.productcategory.ProdId. Te dwa przypisania różnią się, pierwszy dotyczy produktu (połączony z productcategory), drugi dotyczy productcategorypołączenia z obydwoma producti category. Czy podążasz za moim rozumowaniem?
CiccioMiami

5

spójrz na ten przykładowy kod z mojego projektu

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

w tym kodzie dołączyłem 3 tabele i wyplułem warunek złączenia z klauzuli where

uwaga: klasy Services są po prostu wypaczone (hermetyzujące) operacje na bazie danych


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
Chociaż ten fragment kodu może rozwiązać problem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod.
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}");
        }

proste, ale jest lepsze z exp lambda, jak niektórzy mówili.
Alex Martinez,

0

minęło trochę czasu, ale moja odpowiedź może komuś pomóc:

jeśli już poprawnie zdefiniowałeś relację, możesz użyć tego:

        var res = query.Products.Select(m => new
        {
            productID = product.Id,
            categoryID = m.ProductCategory.Select(s => s.Category.ID).ToList(),
        }).ToList();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.