Cómo realizar una unión entre varias tablas en LINQ lambda

91

Estoy tratando de realizar una unión entre varias tablas en LINQ. Tengo las siguientes clases:

Product {Id, ProdName, ProdQty}

Category {Id, CatName}

ProductCategory{ProdId, CatId} //association table

Y uso el siguiente código (donde product, categoryy productcategoryson instancias de las clases anteriores):

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

Con este código obtengo un objeto de la siguiente clase:

QueryClass { productproductcategory, category}

Donde producproductcategory es de tipo:

ProductProductCategoryClass {product, productcategory}

No entiendo dónde está la "tabla" unida, esperaba una sola clase que contenga todas las propiedades de las clases involucradas.

Mi objetivo es poblar otro objeto con algunas propiedades resultantes de la consulta:

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

¿Cómo puedo lograr este objetivo?

CiccioMiami
fuente
No entendí ... ¿ por qué m.ProdId = ??? en lugar de prodId = m.ProdId ?
Adriano Repetti
Porque no sé de antemano cómo navegar y obtener ProdId
CiccioMiami

Respuestas:

181

Para las uniones, prefiero la sintaxis de consulta para todos los detalles que están felizmente ocultos (entre los que se encuentran los identificadores transparentes involucrados con las proyecciones intermedias en el camino que son evidentes en el equivalente de sintaxis de puntos). Sin embargo, preguntaste sobre Lambdas, que creo que tienes todo lo que necesitas, solo necesitas juntarlo todo.

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 es necesario, puede guardar la combinación en una variable local y reutilizarla más tarde, sin embargo, al carecer de otros detalles al contrario, no veo ninguna razón para introducir la variable local.

Además, podría lanzar el Selecten la última lambda del segundo Join(nuevamente, siempre que no haya otras operaciones que dependan de los resultados de la combinación) que darían:

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

... y haciendo un último intento de venderle la sintaxis de consulta, esto se vería así:

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

Sus manos pueden estar atadas sobre si la sintaxis de consulta está disponible. Sé que algunas tiendas tienen tales mandatos, a menudo basados ​​en la noción de que la sintaxis de consulta es algo más limitada que la sintaxis de puntos. Hay otras razones, como "¿por qué debería aprender una segunda sintaxis si puedo hacer todo y más con la sintaxis de puntos?" Como muestra esta última parte, hay detalles que la sintaxis de consulta oculta que pueden hacer que valga la pena adoptarla con la mejora en la legibilidad que aporta: todas esas proyecciones intermedias e identificadores que tiene que cocinar felizmente no están al frente y al centro. etapa en la versión de sintaxis de consulta: son pelusas de fondo. Fuera de mi caja de jabón ahora, de todos modos, gracias por la pregunta. :)

devgeezer
fuente
3
Gracias tu solución es más completa. Estoy de acuerdo en que la sintaxis de la consulta en algunos casos es más clara, pero lo has adivinado bien, me han pedido que use lambda. Además, tengo que hacer estas uniones en 6 tablas, y la notación de puntos en este caso es más ordenada
CiccioMiami
@devgeezer ¿Qué pasa si necesitamos una condición adicional en la JOINdeclaración? ¿Como hacemos eso? Por ejemplo, en la join pc in productcategory on p.Id equals pc.ProdIdlínea, necesitamos agregar and p.Id == 1.
Helicóptero de ataque Harambe
Parece sospechoso que lo desee, p.Id == 1ya que es más un filtro de dónde que un criterio de unión. La manera en que se hace una combinación de más de un criterio general es el uso de un tipo anónimo: join pc in productcategory on new { Id = p.Id, Other = p.Other } equals new { Id = pc.ProdId, Other = pc.Other }. Esto funciona en Linq-to-Objects, y supongo que lo mismo funcionará también con consultas de base de datos. Con las bases de datos, es posible que pueda prescindir de consultas de combinación complicadas definiendo claves externas según corresponda y accediendo a los datos relacionados a través de la propiedad relacionada.
devgeezer
Gracias por la solución limpia.
Thomas.Benz
En su ejemplo: en la sintaxis de puntos, ppc ppc.p son tipos anónimos, ¿verdad? En la sintaxis de la consulta, el p.id que usa en la última selección sigue siendo un objeto de producto, ¿verdad? Entonces, con la sintaxis de consulta es más fácil si une varias tablas para realizar operaciones en el shema de retorno final como min minby?
CDrosos
12

Lo que ha visto es lo que obtiene, y es exactamente lo que pidió, aquí:

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

Esa es una expresión lambda que devuelve un tipo anónimo con esas dos propiedades.

En sus CategorizedProducts, solo necesita pasar por esas propiedades:

CategorizedProducts catProducts = query.Select(
      m => new { 
             ProdId = m.productproductcategory.product.Id, 
             CatId = m.category.CatId, 
             // other assignments 
           });
Jon Skeet
fuente
Gracias. Entiendo la discusión sobre la clase anónima, pero sus propiedades contienen solo los objetos de clase que cumplen con la consulta. ¿Y qué sucede después de realizar la 2 combinación? productproductcategory.product no está unido a la categoría, ¿verdad?
CiccioMiami
@CiccioMiami: Bueno, las propiedades son referencias a objetos, sí. No está muy claro lo que quiere decir con "no unido": ¿qué información no obtiene de su consulta que desea obtener?
Jon Skeet
Con la primera unión, obtengo la unión entre productos y categoría de producto. Con el segundo, obtengo la unión entre categoría de producto (producto unido) y categoría. Eso significa que la información sobre la combinación múltiple solo se incluye en productproductcategory. Esto significa que el producto (y la categoría) simplemente se unen con la categoría de producto.
CiccioMiami
1
@CiccioMiami: Lo siento, no te estoy siguiendo, pero si especificas la unión, lo hará. ¿Has intentado usar el código en mi respuesta? ¿No hace lo que quieres?
Jon Skeet
Lo siento, quería acceder a tu código. La asignación de CatId obras funciona bien. Porque ProdIddebería ser m.productproductcategory.product.IdOR m.productproductcategory.productcategory.ProdId. Las dos asignaciones difieren, la primera es sobre el producto (unida con productcategory) la segunda es con productcategoryunida a ambos producty category. ¿Sigues mi razonamiento?
CiccioMiami
5

mira este código de muestra de mi proyecto

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

en este código me uní a 3 tablas y escupí la condición de unión desde la cláusula where

nota: las clases de servicios simplemente se deforman (encapsulan) las operaciones de la base de datos

Basheer AL-MOMANI
fuente
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
fuente
1
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo a la pregunta para los lectores en el futuro, y es posible que esas personas no conozcan los motivos de su sugerencia 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}");
        }
Alex Martinez
fuente
simple eso, pero es mejor con lambda exp, como dijeron algunas personas.
Alex Martinez
0

ha pasado un tiempo pero mi respuesta puede ayudar a alguien:

si ya definió la relación correctamente, puede usar esto:

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