如何在 LINQ lambda 中执行多个表之间的联接

Posted

技术标签:

【中文标题】如何在 LINQ lambda 中执行多个表之间的联接【英文标题】:How to perform Join between multiple tables in LINQ lambda 【发布时间】:2012-03-31 23:45:55 【问题描述】:

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

Product Id, ProdName, ProdQty

Category Id, CatName

ProductCategoryProdId, 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

productproductcategory 的类型为:

ProductProductCategoryClass product, productcategory

我不明白连接的“表”在哪里,我期待一个包含相关类的所有属性的单个类

我的目标是用查询产生的一些属性填充另一个对象:

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

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

【问题讨论】:

我不明白...为什么是 m.ProdId = ??? 而不是 prodId = m.ProdId 因为我事先不知道如何导航和获取 ProdId 【参考方案1】:

对于连接,对于所有被愉快地隐藏的细节,我非常喜欢查询语法(其中最重要的是与中间投影相关的透明标识符,这些标识符在点语法等效项中很明显)。但是,您询问了 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
    );

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

此外,您可以将 Select 扔到第二个 Join 的最后一个 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 
        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
    ;

您可能会纠结于查询语法是否可用。我知道有些商店有这样的要求——通常是基于查询语法比点语法更受限制的概念。还有其他原因,例如“如果我可以在点语法中做所有事情甚至更多,我为什么要学习第二种语法?”正如最后一部分所示 - 查询语法隐藏的一些细节可以使其非常值得拥抱它带来的可读性改进:所有那些你必须制作的中间投影和标识符很高兴不是前端和中心 -查询语法版本中的阶段 - 它们是背景绒毛。现在离开我的肥皂盒 - 无论如何,谢谢你的问题。 :)

【讨论】:

感谢您的解决方案更完整。我同意在某些情况下查询语法更清晰,但你猜对了,我被要求使用 lambda。此外,我必须对 6 个表进行此连接,并且这种情况下的点表示法更整洁 @devgeezer 如果我们需要在JOIN 语句中添加条件怎么办?我们如何做到这一点?例如join pc in productcategory on p.Id equals pc.ProdId这一行,我们需要添加and p.Id == 1 似乎怀疑你想要p.Id == 1,因为这更像是一个过滤器而不是一个连接条件。您根据多个标准进行连接的方式通常是使用匿名类型:join pc in productcategory on new Id = p.Id, Other = p.Other equals new Id = pc.ProdId, Other = pc.Other 。这适用于 Linq-to-Objects,我认为同样适用于数据库查询。使用数据库,您可以通过适当地定义外键并通过相关属性访问相关数据来放弃复杂的连接查询。 感谢您提供干净的解决方案。 在您的示例中:在点语法中, ppc ppc.p 是匿名类型,对吗?在查询语法中,您在最后一次选择中使用的 p.id 仍然是产品对象,对吗?因此,如果您加入多个表以在最终返回的 shema 中执行操作,例如 min minby?,使用查询语法会更容易【参考方案2】:

所见即所得 - 这正是您所要求的,在这里:

(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:嗯,属性是对象的引用,是的。您说的“未加入”是什么意思并不清楚 - 您没有从您想要获得的查询中获得哪些信息? 第一次加入时,我得到了产品和产品类别之间的加入。第二个我得到产品类别(加入的产品)和类别之间的连接。这意味着有关多重连接的信息仅包含在 productproductcategory 中。这意味着产品(和类别)只是与 productcategory 连接。 @CiccioMiami:对不起,我没有关注你 - 但如果你指定加入,它会这样做。您是否尝试过使用我回答中的代码?它没有做你想做的事吗? 抱歉,我想获取您的代码。 CatId 的分配工作正常。对于ProdId,它应该是m.productproductcategory.product.Idm.productproductcategory.productcategory.ProdId。这两个分配不同,第一个是在产品上(与productcategory 连接),第二个是与productcategory 并用productcategory。你遵循我的推理吗?【参考方案3】:

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

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 子句中忽略了加入条件

注意:服务类只是扭曲(封装)数据库操作

【讨论】:

【参考方案4】:
 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);



       

【讨论】:

虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。【参考方案5】:
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 效果更好。【参考方案6】:

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

如果你已经正确定义了关系,你可以使用这个:

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

【讨论】:

以上是关于如何在 LINQ lambda 中执行多个表之间的联接的主要内容,如果未能解决你的问题,请参考以下文章

如何在 LINQ 和 Lambda 表达式 LINQ C# 上正确执行 SQL 查询

在LINQ中组合多个表

C# linq 多表关联

如何使用 Lambda 或 Linq 将匿名类型转换为原始类型成员

Lambda 表达式 LINQ 等效于 SQL 在同一表/变量上存在查询

如何将带有内连接的 sql 查询转换为 linq lambda 表达式?