Como realizar a junção entre várias tabelas em LINQ lambda


89

Estou tentando realizar uma junção entre várias tabelas no LINQ. Tenho as seguintes aulas:

Product {Id, ProdName, ProdQty}

Category {Id, CatName}

ProductCategory{ProdId, CatId} //association table

E eu uso o seguinte código (onde product, categorye productcategorysão instâncias das classes acima):

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

Com este código, obtenho um objeto da seguinte classe:

QueryClass { productproductcategory, category}

Onde productproductcategory é do tipo:

ProductProductCategoryClass {product, productcategory}

Não entendo onde está a "tabela" associada, esperava uma única classe que contivesse todas as propriedades das classes envolvidas.

Meu objetivo é preencher outro objeto com algumas propriedades resultantes da consulta:

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

como posso atingir esse objetivo?


Não entendi ... porque m.ProdId = ??? em vez de prodId = m.ProdId ?
Adriano Repetti

Porque eu não sei com antecedência como navegar e obter prodid
CiccioMiami

Respostas:


179

Para junções, eu prefiro fortemente a sintaxe de consulta para todos os detalhes que ficam ocultos (não menos importante dos quais são os identificadores transparentes envolvidos com as projeções intermediárias ao longo do caminho que são aparentes no equivalente de sintaxe de ponto). No entanto, você perguntou sobre Lambdas, eu acho que você tem tudo o que precisa - você só precisa colocar tudo junto.

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

Se necessário, você pode salvar a junção em uma variável local e reutilizá-la mais tarde; porém, na falta de outros detalhes ao contrário, não vejo razão para introduzir a variável local.

Além disso, você poderia lançar o Selectno último lambda do segundo Join(novamente, desde que não haja outras operações que dependam dos resultados da junção) que daria:

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

... e fazendo uma última tentativa de convencê-lo da sintaxe de consulta, ficaria assim:

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

Suas mãos podem estar atadas quanto à disponibilidade de sintaxe de consulta. Eu sei que algumas lojas têm tais mandatos - muitas vezes com base na noção de que a sintaxe de consulta é um pouco mais limitada do que a sintaxe de ponto. Existem outras razões, como "por que devo aprender uma segunda sintaxe se posso fazer tudo e muito mais na sintaxe de pontos?" Como mostra esta última parte - há detalhes que a sintaxe de consulta oculta que podem fazer valer a pena abraçar com a melhoria da legibilidade que ela traz: todas as projeções intermediárias e identificadores que você precisa preparar felizmente não são front-and-center- estágio na versão de sintaxe de consulta - eles são fluff de fundo. Fora da minha caixa de sabão agora - de qualquer forma, obrigado pela pergunta. :)


3
Obrigado, sua solução é mais completa. Concordo que a sintaxe da consulta em alguns casos é mais clara, mas você acertou, pediram-me para usar lambda. Além disso, tenho que fazer isso junta mais de 6 tabelas, e a notação de ponto nesse caso é mais legal
CiccioMiami

@devgeezer E se precisarmos de uma condição de adição na JOINinstrução? Como fazemos isso? Por exemplo, na join pc in productcategory on p.Id equals pc.ProdIdlinha, precisamos adicionar and p.Id == 1.
Helicóptero de Ataque Harambe

Parece suspeito que você deseja, p.Id == 1pois isso é mais um filtro where do que um critério de junção. A maneira como você faria uma junção de mais de um critério geral é usar um tipo anônimo: join pc in productcategory on new { Id = p.Id, Other = p.Other } equals new { Id = pc.ProdId, Other = pc.Other }. Isso funciona no Linq-to-Objects e presumo que o mesmo funcione com consultas de banco de dados também. Com os bancos de dados, você pode ignorar consultas complicadas de junção definindo chaves estrangeiras conforme apropriado e acessando dados relacionados por meio da propriedade relacionada.
devgeezer

Obrigado pela solução limpa.
Thomas.Benz

No seu exemplo: na sintaxe de ponto, o ppc ppc.p são tipos anônimos, certo? Na sintaxe da consulta, o p.id que você usa na última seleção ainda é um objeto de produto, certo? Portanto, a sintaxe de consulta é mais fácil se você juntar várias tabelas para fazer operações no shema de retorno final, como min minby?
CDrosos

12

O que você viu é o que você obtém - e é exatamente o que você pediu, aqui:

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

Essa é uma expressão lambda que retorna um tipo anônimo com essas duas propriedades.

Em seus CategorizedProducts, você só precisa acessar essas propriedades:

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

Obrigado. Eu entendo a discussão sobre a classe anônima, mas suas propriedades contêm apenas os objetos de classe que preenchem a consulta. E o que acontece depois que eu executar o 2 join? productproductcategory.product não está associado à categoria, certo?
CiccioMiami de

@CiccioMiami: Bem, as propriedades são referências a objetos, sim. Não está muito claro o que você quer dizer com "não participado" - quais informações você não obtém de sua consulta?
Jon Skeet de

Com a primeira associação, obtenho a associação entre produtos e categoria de produtos. Com o segundo, obtenho a junção entre categoria de produto (produto associado) e categoria. Isso significa que as informações sobre a junção múltipla estão contidas apenas na categoria produtoproduto. Isso significa que o produto (e a categoria) são unidos apenas à categoria do produto.
CiccioMiami de

1
@CiccioMiami: Desculpe, não estou te seguindo - mas se você especificar o ingresso, ele servirá. Você tentou usar o código em minha resposta? Não faz o que você quer?
Jon Skeet de

Desculpe, queria obter o seu código. A atribuição de CatId obras bem. Pois ProdIddeve ser m.productproductcategory.product.IdOR m.productproductcategory.productcategory.ProdId. As duas atribuições são diferentes, a primeira está no produto (unido com productcategory) e a segunda está com productcategoryunido com ambos producte category. Você segue meu raciocínio?
CiccioMiami de

4

dê uma olhada neste código de amostra do meu projeto

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

neste código juntei 3 tabelas e coloquei a condição de junção da cláusula where

nota: as classes de serviços são apenas distorcidas (encapsulam) as operações do banco de dados


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
Embora este snippet de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade de sua postagem. Lembre-se de que você está respondendo à pergunta para leitores no futuro e essas pessoas podem não saber os motivos de sua sugestão de código.
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}");
        }

simples apenas isso, mas é melhor com lambda exp como algumas pessoas disseram.
Alex Martinez

0

já faz um tempo, mas minha resposta pode ajudar alguém:

se você já definiu a relação corretamente, você pode usar isto:

        var res = query.Products.Select(m => new
        {
            productID = product.Id,
            categoryID = m.ProductCategory.Select(s => s.Category.ID).ToList(),
        }).ToList();
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.