如何在LINQ Lambda中的多个表之间执行Join


89

我试图在LINQ中的多个表之间执行联接。我有以下课程:

Product {Id, ProdName, ProdQty}

Category {Id, CatName}

ProductCategory{ProdId, CatId} //association table

我用下面的代码(其中productcategoryproductcategory是在上述类的实例):

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

我怎样才能实现这个目标?


我不明白...为什么m.ProdId = ??? 而不是prodId = m.ProdId
Adriano Repetti 2012年

因为我事先不知道如何导航和获取ProdId
CiccioMiami 2012年

Answers:


179

对于联接,我强烈希望对所有隐藏的详细信息使用查询语法(并非最不重要的是中间投影所涉及的透明标识符,在点语法等效项中很明显)。但是,您询问了Lambda,我认为您拥有所需的一切-您只需要将它们放在一起即可。

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

如果需要,可以将联接保存到局部变量中,并在以后重用,但是由于缺少其他细节,我认为没有理由引入局部变量。

另外,您可以将s Select放入第二个的最后一个lambda中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
    };

您的双手可能会被束缚在查询语法是否可用上。我知道有些商店有这样的要求-通常是基于这样的概念,即查询语法比点语法受到更多限制。还有其他原因,例如“如果我可以用点语法做更多的事情,为什么还要学习第二种语法?” 正如最后一部分所显示的那样-查询语法隐藏的一些细节可以使其值得一读,因为它带来了可读性的提高:所有您需要准备的中间投影和标识符都不必位于中心位置语法版本中的阶段-它们是背景绒毛。现在关闭我的肥皂盒-无论如何,谢谢您的提问。:)


3
谢谢,您的解决方案更加完整。我同意在某些情况下查询语法更清晰,但您猜对了,有人要求我使用lambda。此外,我必须在6个表上进行此联接,这种情况下的点符号更加简洁
CiccioMiami 2012年

@devgeezer如果JOIN语句中需要加条件怎么办?我们该怎么做?例如,在这一join pc in productcategory on p.Id equals pc.ProdId行中,我们需要添加and p.Id == 1
Harambe攻击直升机

似乎怀疑您想要的是,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

感谢您提供干净的解决方案。
Thomas.Benz

在您的示例中:在点语法中,ppc ppc.p是匿名类型,对吗?在查询语法中,您在最后一次选择中使用的p.id仍然是产品对象,对吗?因此,如果您联接多个表以在最终返回的shema中进行操作(例如min minby),那么使用查询语法会更容易?
CDrosos

12

您所看到的就是您所得到的-这正是您想要的,在这里:

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

那是一个lambda表达式,返回带有这两个属性的匿名类型。

在您的CategorizedProducts中,您只需要浏览以下属性:

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

谢谢。我了解有关匿名类的讨论,但其属性仅包含满足查询的类对象?我执行2次加入后会发生什么?productproductcategory.product未加入类别,对吗?
CiccioMiami 2012年

@CiccioMiami:好的,属性是对对象的引用,是的。不清楚“未加入”的含义是什么-您想从查询中获取哪些信息?
乔恩·斯基特

通过第一个联接,我得到了产品和产品类别之间的联接。在第二个中,我获得了productcategory(联接产品)和category之间的联接。这意味着有关多重联接的信息仅包含在productproductcategory中。这意味着产品(和类别)仅与productcategory结合在一起。
CiccioMiami 2012年

1
@CiccioMiami:抱歉,我没有关注您-但如果您指定加入,它将成功。您是否尝试过在我的答案中使用代码?它不做你想要的吗?
乔恩·斯基特

抱歉,我想获取您的代码。工作分配CatId 很好。因为ProdId它应该是m.productproductcategory.product.IdOR m.productproductcategory.productcategory.ProdId。两种分配方式不同,第一种是在产品上(与结合productcategory),第二种是productcategory在两者product和上结合category。你听我的话吗
CiccioMiami 2012年

4

看一下我项目中的这个示例代码

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
尽管此代码段可以解决问题,但提供说明确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。
罗伯·朗格博士(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}");
        }

就这么简单,但是像一些人所说的那样,使用lambda exp会更好。
Alex Martinez '18

0

已经有一段时间了,但是我的回答可能会帮助某人:

如果您已经正确定义了关系,则可以使用以下方法:

        var res = query.Products.Select(m => new
        {
            productID = product.Id,
            categoryID = m.ProductCategory.Select(s => s.Category.ID).ToList(),
        }).ToList();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.