LINQ多列连接

时间:2011-12-30 07:08:27

标签: linq join left-join multiple-columns

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

但是,根据产品的使用年限收到了额外差异化的要求,因此有必要修改原始查询。

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

  

join子句中某个表达式的类型不正确。调用“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 is new {'b productKey,int productId,string age,...}

     

'b是新的{int productId,字符串年龄}

由于两者都是'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众神可以看到解决方案。

2 个答案:

答案 0 :(得分:0)

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

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专门订购

答案 1 :(得分:0)

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

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

    new {pr.productId,pr.ageId} 新{pr.code,pr.product,pr.uom} 分组到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冒着时间提出建议。如果您找到任何改进方法,请告诉我。