【问题标题】:LINQ Multiple Column JoinLINQ 多列连接
【发布时间】:2011-12-30 07:08:27
【问题描述】:

我们的初始查询由许多可以正常工作的子查询组成,这些子查询基于使用单个列 (productId) 的连接。生成的模型映射到一个网格,其中列出了产品名称以及昨天、今天和明天各自所需的数量。

但是,收到了基于产品使用年限的附加差异化因素的要求,因此有必要修改原始查询。

因此,以下代码是对工作代码的修改,它使用单个字段 ProductId 作为键。在尝试修改查询以使用多列键(ProductId 和 Age)时,我遇到了麻烦,收到以下错误:

连接子句中的其中一个表达式的类型不正确。调用“GroupJoin”时类型推断失败。

在前面创建 Distinct Aggregate 的查询中,我将键更改为 ProductId 和 age 的组合,并将匿名类型的新成员 productKey 分配给 new { pr.productId , pr.age }。然后在最后的查询中,我试图加入这个结果 on productKey equals new { y.productId, y.age } (y 代表加入结果“昨天”)。

当我将鼠标悬停在连接结果的每个键(gr.productKey 和 y.productKey)上时,每个键都会显示以下内容:

'b'a.productKey

匿名类型:

'a 是新的 {'b productKey, int productId, string age,... }

'b 是新的 {int productId, string age}

由于两者都是 'b 类型的 new {int productId, string age} 我期待成功;但是,编译器仍然不合作。我相信一个 new {int,string} 与另一个相同,无论名称如何。

 var yesterday = from p in productOrdered
                 where p.deliveryDate.Date == DateTime.Now.AddDays(-1).Date
                 group p by new { p.id, p.age } into g
                 orderby g.Key
                 select new {
                     productKey = g.Key,
                     productId = g.Max(s => s.id),
                     age = g.Max(s => s.age),
                     quantity = g.Count(),
                     weight = g.Sum(s => s.weight), 
                 };

 var grp = (from pr in prods2
            group pr by new { pr.productId, pr.age } into g
            orderby g.Key
            select new {
                productKey = g.Key,
                productId = g.Max(s => s.productId),
                age = g.Max(s => s.age),
                code = g.Max(s => s.code),
                product = g.Max(s => s.product),
                uom = g.Max(s => s.uom)
            }).Distinct();

var model = from gr in grp
            join y in yesterday on gr.productKey equals new { y.productId, y.age } into outer0
            from y in outer0.DefaultIfEmpty()
            join n in now on gr.productKey equals new { n.productId, n.age } into outer1
            from n in outer1.DefaultIfEmpty()
            join t in tomorrow on gr.productKey equals new { t.productId, t.age } into outer2
            from t in outer2.DefaultIfEmpty()
            select new RequiredProductsViewModel
            {
                ProductId = gr.productId,
                Aged = gr.age,
                Code = gr.code.ToString(),
                Description = gr.product.ToString(),
                MinusQ = (!(null == y) ? y.quantity : 0),
                MinusW = (!(null == y) ? decimal.Parse(y.weight.ToString()) : 0),
                ZeroQ = (!(null == n) ? n.quantity : 0),
                ZeroW = (!(null == n) ? decimal.Parse(n.weight.ToString()) : 0),
                OneQ = (!(null == t) ? t.quantity : 0),
                OneW = (!(null == t) ? decimal.Parse(t.weight.ToString()) : 0),
                UofM = gr.uom.ToString()
            };

在 LINQPad 中进行测试得到了类似的结果,我还根据本网站上的类似问题尝试了几种变体,例如但不限于以下问题:

在昨天加入 y 新的 {Key1 = gr.productId,Key2 = gr.age} 等于 y.productKey 进入 outer0

在昨天加入 y new { gr.productId, gr.age } 等于 y.productKey 进入 outer0

同样,此修改所依据的原始查询成功运行。我很确定这是“一点知识,是一件危险的事情”问题之一。或者也许只是“小知识”问题。无论哪种方式,我都希望 LINQ 大神们能找到解决方案。

【问题讨论】:

    标签: linq join left-join multiple-columns


    【解决方案1】:

    尝试为多列键声明命名类型,而不是使用匿名:

    public class ProductKey
    {
        public int ProductId { get; set; }
    
        public int ProductAge { get; set; }
    }
    

    在“group by”和“join”子句中使用此 ProductKey。 所以您的查询将如下所示:

                   var yesterday = from p in productOrdered
                     where p.deliveryDate.Date == DateTime.Now.AddDays(-1).Date
                     group p by new ProductKey { ProductId=p.id, ProductAge=p.age } into g
                     orderby g.Key.ProductId
                     select new {
                         productKey = g.Key,
                         productId = g.Max(s => s.id),
                         age = g.Max(s => s.age),
                         quantity = g.Count(),
                         weight = g.Sum(s => s.weight), 
                     };
    
     var grp = (from pr in prods2
                group pr by new ProductKey{ ProductId=pr.productId, ProductKey=pr.age } into g
                orderby g.Key.ProductId
                select new {
                    productKey = g.Key,
                    productId = g.Max(s => s.productId),
                    age = g.Max(s => s.age),
                    code = g.Max(s => s.code),
                    product = g.Max(s => s.product),
                    uom = g.Max(s => s.uom)
                }).Distinct();
    
    var model = from gr in grp
                join y in yesterday on gr.productKey equals new ProductKey { ProductId=y.productId, ProductAge=y.age } into outer0
                from y in outer0.DefaultIfEmpty()
                join n in now on gr.productKey equals new ProductKey { ProductId=n.productId, ProductAge=n.age } into outer1
                from n in outer1.DefaultIfEmpty()
                join t in tomorrow on gr.productKey equals new ProductKey { ProductId=t.productId, ProductAge=t.age } into outer2
                from t in outer2.DefaultIfEmpty()
                select new RequiredProductsViewModel
                {
                    ProductId = gr.productId,
                    Aged = gr.age,
                    Code = gr.code.ToString(),
                    Description = gr.product.ToString(),
                    MinusQ = (!(null == y) ? y.quantity : 0),
                    MinusW = (!(null == y) ? decimal.Parse(y.weight.ToString()) : 0),
                    ZeroQ = (!(null == n) ? n.quantity : 0),
                    ZeroW = (!(null == n) ? decimal.Parse(n.weight.ToString()) : 0),
                    OneQ = (!(null == t) ? t.quantity : 0),
                    OneW = (!(null == t) ? decimal.Parse(t.weight.ToString()) : 0),
                    UofM = gr.uom.ToString()
                };
    

    更新:

    带有 ProductKey 类的 ORDER BY 子句会报错(linq 不知道如何对多列类进行排序)所以你应该专门按 g.Key.ProductId 排序

    【讨论】:

    • 当我进行上述更改时,我收到一条错误消息,指出“至少一个对象必须实现 IComparable”。这显示在所有上述查询中,“昨天”、“grp”和“模型”,以及那些未显示的查询(“现在”和“明天”),但与查询“昨天”相同。
    • @user946045:IComparable 定义了 CompareTo 方法,该方法告诉如何比较类的两个实例。它是订购所必需的,但在这种情况下,我认为最好按特定列订购 - aswer updated
    • 到目前为止,创建 productKey 类似乎是倒退了 2 步。当我对最后一个查询之前的所有查询使用匿名类型时,我在每一步都会得到准确的结果。当我使用一个已定义的类时,我得到一个 IComparable 错误,在任何情况下我都没有使用 Order By 子句。
    • @user946045:即使没有 orderby 也会出现 IComparable 错误?您在“昨天”和“grp”查询中有 orderby 子句。你能发布你当前的查询吗?
    • 谢谢。哇,我一定是瞎了眼,或者这只是凌晨 4 点的因素。所以让我先测试一些东西,然后再发帖。
    【解决方案2】:

    以下更改似乎产生了所需的结果。

    1. 在依赖查询中分组已更改为 new { p.id, p.ageId}
    2. 所有 orderby 子句都从基于 g.Key 的单个 orderby 修改为基于 g.Key.idg.Key 的 2 个单独的 orderby 子句.ageId
    3. 最后,在定义 Left 表的查询中,我使用了以下内容:

    new { pr.code, pr.product, pr.uom}new { pr.productId, pr.ageId} 分组到 g

    我以前曾在另一种方法中成功使用过这种变体,但忘记了我在哪里遇到它。它当然精确地定义了字段和复合键。

    此方法现在会生成订购产品的汇总列表,其中包含其数量和重量的总和。此外,不同年龄要求的产品单独列出。最终,我们会得到一个产品列表,其中仅显示已订购的产品,按年龄分组并显示数量和重量,包括过期订单、今天的订单和明天的订单。

    我已包含此方法的所有代码,既是对某些人的帮助,也是对那些有更高技能的人寻求改进的挑战。

        [GridAction]
        public ActionResult AjaxOps_ActionList() {
    
            var orders = salesOrderHeaderRepository.All.ToArray();
            var details = salesOrderDetailRepository.All.ToArray();
            var ages = ageRepository.All.ToArray();
            var custAges = customerAgeRepository.All.ToArray();
            var kinds = foodKindRepository.All.ToArray();
            var types = foodTypeRepository.All.ToArray();
            var units = unitOfMeasureRepository.All.ToArray();
    
            var products = from p in productRepository.All.ToArray()
                           select new {
                               productId = p.ProductId,
                               code = p.Name,
                               typeId = p.TypeId,
                               kindId = p.KindId,
                               Description = p.Description,
                               unitId = p.UnitId,
                               weight = (p == null) ? 0 : p.AverageWeight
                           };
    
            var productOrdered = from o in orders
                                 join d in details on o.SalesOrderHeaderId equals d.SalesOrderId
                                 join c in custAges on o.CustomerId equals c.CustomerId
                                 join a in ages on c.AgeId equals a.AgeId
                                 join p in products on d.ProductId equals p.productId
                                 select new {
                                     id = d.ProductId,
                                     code = p.code,
                                     ageId = a.AgeId,
                                     quantity = (null == d) ? 0 : d.Quantity,
                                     weight = ((null == d) ? 0 : d.Quantity) * ((null == p) ? 0 : p.weight),
                                     deliveryDate = o.DeliveryDateTime
                                 };
    
            var tomorrow = from p in productOrdered
                           where p.deliveryDate.Date == DateTime.Now.AddDays(1).Date
                           group p by new { p.id, p.ageId} into g
                           orderby g.Key.id
                           orderby g.Key.ageId
                           select new {
                               productId = g.Key.id,
                               ageId = g.Key.ageId,
                               quantity = g.Count(),
                               weight = g.Sum(s => s.weight)
                           };
    
            var now = from p in productOrdered
                      where p.deliveryDate.Date == DateTime.Now.Date
                      group p by new { p.id, p.ageId } into g
                      orderby g.Key.id
                      orderby g.Key.ageId
                      select new {
                          productId = g.Key.id,
                          ageId = g.Key.ageId,
                          quantity = g.Count(),
                          weight = g.Sum(s => s.weight)
                      };
    
            var yesterday = from p in productOrdered
                            where p.deliveryDate.Date == DateTime.Now.AddDays(-1).Date
                            group p by new { p.id, p.ageId } into g
                            orderby g.Key.id
                            orderby g.Key.ageId
                            select new {
                                productId = g.Key.id,
                                ageId = g.Key.ageId,
                                quantity = g.Count(),
                                weight = g.Sum(s => s.weight)
                            };
    
            var prods = from pr in products
                        join p in productOrdered on pr.productId equals p.id
                        join t in types on pr.typeId equals t.FoodTypeId
                        join k in kinds on pr.kindId equals k.FoodKindId
                        join u in units on pr.unitId equals u.AUnitMeasureId
                        select new {
                            productId = pr.productId,
                            ageId = p.ageId,
                            code = pr.code,
                            product = t.Name + " " + k.Name + " " + pr.Description,
                            uom = u.Name
                        };
    
            var grp = (from pr in prods
                      group new { pr.code, pr.product, pr.uom} by new { pr.productId, pr.ageId} into g
                      orderby g.Key.productId
                      orderby g.Key.ageId
                      select new {
                          productKey = g.Key,
                          productId = g.Key.productId,
                          ageId = g.Key.ageId,
                          code = g.Max(s => s.code),
                          product = g.Max(s => s.product),
                          uom = g.Max(s => s.uom)
                      }).Distinct();
    
            var model = from gr in grp
                        join y in yesterday on gr.productKey equals new { y.productId, y.ageId } into outer0
                        from y in outer0.DefaultIfEmpty()
                        join n in now on gr.productKey equals new { n.productId, n.ageId } into outer1
                        from n in outer1.DefaultIfEmpty()
                        join t in tomorrow on gr.productKey equals new { t.productId, t.ageId } into outer2
                        from t in outer2.DefaultIfEmpty()
                        select new RequiredProductsViewModel
                        {
                            ProductId = gr.productId,
                            Code = gr.code.ToString(),
                            Description = gr.product.ToString(),
                            AgeId = gr.ageId,
                            MinusQ = (!(null == y) ? y.quantity : 0),
                            MinusW = (!(null == y) ? decimal.Parse(y.weight.ToString()) : 0),
                            ZeroQ = (!(null == n) ? n.quantity : 0),
                            ZeroW = (!(null == n) ? decimal.Parse(n.weight.ToString()) : 0),
                            OneQ = (!(null == t) ? t.quantity : 0),
                            OneW = (!(null == t) ? decimal.Parse(t.weight.ToString()) : 0),
                            UofM = gr.uom.ToString()
                        };
    
            return View(new GridModel<RequiredProductsViewModel>
            {
                Data = model
            });
    

    很可能会有其他(也许更好)的解决方案;但是,这是有效的,这是我的故事,我会坚持下去。

    最后感谢 PanJanek 抽出时间提出建议。如果您找到任何改进的方法,请告诉我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-10
      • 2012-10-21
      • 1970-01-01
      • 2021-10-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多