Как выполнить соединение между несколькими таблицами в LINQ lambda

91

Я пытаюсь выполнить соединение между несколькими таблицами в LINQ. У меня есть следующие занятия:

Product {Id, ProdName, ProdQty}

Category {Id, CatName}

ProductCategory{ProdId, CatId} //association table

И я использую следующий код (где product, categoryи productcategoryявляются экземплярами вышеуказанных классов):

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

С помощью этого кода я получаю объект из следующего класса:

QueryClass { productproductcategory, category}

Если категория продукта имеет тип:

ProductProductCategoryClass {product, productcategory}

Я не понимаю, где находится объединенная «таблица», я ожидал, что будет один класс , содержащий все свойства из задействованных классов.

Моя цель - заполнить другой объект некоторыми свойствами, полученными в результате запроса:

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

как я могу достичь этой цели?

CiccioMiami
источник
Я не понял ... почему m.ProdId = ??? вместо prodId = m.ProdId ?
Адриано Репетти,
Потому что я заранее не знаю, как ориентироваться и получать ProdId
CiccioMiami

Ответы:

181

Для соединений я настоятельно предпочитаю синтаксис запроса для всех деталей, которые удачно скрыты (не последней из которых являются прозрачные идентификаторы, связанные с промежуточными проекциями по пути, которые очевидны в эквиваленте точечного синтаксиса). Однако вы спросили о лямбдах, которые, я думаю, у вас есть все, что вам нужно - вам просто нужно все это собрать.

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

Если вам нужно, вы можете сохранить соединение в локальную переменную и повторно использовать его позже, однако, не имея других деталей, напротив, я не вижу причин для введения локальной переменной.

Кроме того, вы можете добавить Selectв последнюю лямбду второго Join(опять же, при условии, что нет других операций, зависящих от результатов соединения), что даст:

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

... и сделав последнюю попытку продать вам синтаксис запроса, это будет выглядеть так:

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

Ваши руки могут быть связаны с тем, доступен ли синтаксис запроса. Я знаю, что у некоторых магазинов есть такие полномочия - часто на основании того, что синтаксис запроса несколько более ограничен, чем синтаксис точки. Есть и другие причины, например, «зачем мне изучать второй синтаксис, если я могу делать все и даже больше в точечном синтаксисе?» Как показывает эта последняя часть, есть детали, которые скрывает синтаксис запроса, которые могут сделать его достойным внимания с улучшением удобочитаемости, которое он приносит: все эти промежуточные проекции и идентификаторы, которые вам нужно приготовить, к счастью, не являются главными - этап в версии с синтаксисом запроса - это фоновая чушь. Сними мою мыльницу - в любом случае, спасибо за вопрос. :)

Devgeezer
источник
3
Спасибо, ваше решение более полное. Я согласен, что синтаксис запроса в некоторых случаях более понятен, но вы правильно угадали, меня попросили использовать лямбда. Более того, я должен сделать это объединение более 6 таблиц, и точечная нотация в этом случае более аккуратная
CiccioMiami
@devgeezer Что, если нам нужно условие сложения в JOINоператоре? Как мы это делаем? Например, в join pc in productcategory on p.Id equals pc.ProdIdстроке нам нужно добавить and p.Id == 1.
Harambe Attack Helicopter,
Кажется подозрительным, что вы этого хотите, p.Id == 1поскольку это скорее фильтр where, чем критерий соединения. То , как вы бы сделать присоединиться к более чем одному критерию , как правило, чтобы использовать анонимный тип: join pc in productcategory on new { Id = p.Id, Other = p.Other } equals new { Id = pc.ProdId, Other = pc.Other }. Это работает в Linq-to-Objects, и я предполагаю, что то же самое будет работать и с запросами к базе данных. С базами данных вы можете отказаться от сложных запросов на соединение, определив соответствующие внешние ключи и получив доступ к связанным данным через связанное свойство.
devgeezer 03
Спасибо за чистое решение.
Thomas.Benz
В вашем примере: в точечном синтаксисе ppc ppc.p являются анонимными типами, верно? В синтаксисе запроса p.id, который вы использовали в последнем выборе, по-прежнему является объектом продукта, я прав? Итак, с синтаксисом запроса проще, если вы объедините несколько таблиц для выполнения операций в окончательной возвращаемой схеме, например min minby?
CDrosos
12

Вы получаете то, что видели, и это именно то, о чем вы просили:

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

Это лямбда-выражение, возвращающее анонимный тип с этими двумя свойствами.

В ваших категориях продуктов вам просто нужно пройти через эти свойства:

CategorizedProducts catProducts = query.Select(
      m => new { 
             ProdId = m.productproductcategory.product.Id, 
             CatId = m.category.CatId, 
             // other assignments 
           });
Джон Скит
источник
Спасибо. Я понимаю обсуждение анонимного класса, но его свойства содержат только те объекты класса, которые выполняют запрос? И что произойдет после того, как я выполню 2 соединения? productproductcategory.product не объединен с категорией, верно?
CiccioMiami
@CiccioMiami: Да, свойства - это ссылки на объекты. Не совсем понятно, что вы имеете в виду под словом «не присоединились» - какую информацию вы не получаете из своего запроса, которую хотите получить?
Джон Скит
С первым соединением я получаю соединение между продуктами и товарной категорией. Со вторым я получаю соединение между productcategory (объединенный продукт) и категорией. Это означает, что информация о множественном объединении просто содержится в productproductcategory. Это означает, что продукт (и категория) просто объединены с productcategory.
CiccioMiami
1
@CiccioMiami: Извините, я не слежу за вами, но если вы укажете соединение, оно сделает это. Вы пробовали использовать код из моего ответа? Разве это не то, что вы хотите?
Джон Скит
Извините, я хотел перейти к вашему коду. Задание CatId работает нормально. Ибо ProdIdдолжно быть m.productproductcategory.product.IdИЛИ m.productproductcategory.productcategory.ProdId. Эти два назначения различаются: первое относится к продукту (присоединено к productcategory), второе - productcategoryк обоим productи category. Вы понимаете мои рассуждения?
CiccioMiami
5

взгляните на этот пример кода из моего проекта

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

в этом коде я объединил 3 таблицы и выплюнул условие соединения из предложения where

примечание: классы Services просто искажают (инкапсулируют) операции с базой данных

Башир АЛЬ-МОМАНИ
источник
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
Хотя этот фрагмент кода может решить вопрос, включение объяснения действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причины вашего предложения кода.
Д-р Роб Ланг,
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
0

это было давно, но мой ответ может кому-то помочь:

если вы уже правильно определили отношение, вы можете использовать это:

        var res = query.Products.Select(m => new
        {
            productID = product.Id,
            categoryID = m.ProductCategory.Select(s => s.Category.ID).ToList(),
        }).ToList();
iDeveloper
источник