LINQ Multiple Column Join - PullRequest
       23

LINQ Multiple Column Join

1 голос
/ 30 декабря 2011

Наш начальный запрос состоит из множества подзапросов, которые работают правильно, на основе объединений, которые используют один столбец (productId). Полученная модель сопоставляется с сеткой, в которой перечислены названия продуктов вместе с их соответствующими необходимыми количествами на вчера, сегодня и завтра.

Однако было получено требование для дополнительного дифференциатора в зависимости от возраста продукта, и поэтому было необходимо изменить исходный запрос.

Итак, следующий код является модификацией рабочего кода, который использует одно поле, ProductId в качестве ключа. При попытке изменить запрос для использования ключа из нескольких столбцов (ProductId и Age) я столкнулся с проблемой, получив следующую ошибку:

Неверно указан тип одного из выражений в предложении соединения. Ошибка вывода типа при вызове 'GroupJoin'.

В предыдущем запросе, который создает Distinct Aggregate, я изменил ключ на составную часть ProductId и age и назначил новый член анонимного типа, productKey для new {pr.productId, pr.age} . Затем в последнем запросе я пытаюсь объединить этот результат для productKey равно new {y.productId, y.age} (y, представляющий результат объединения "вчера").

При наведении курсора на каждую из клавиш объединенных результатов (gr.productKey и y.productKey) для каждого из них отображается следующее:

'b' a.productKey

Анонимные типы:

'a is new {' b productKey, int productId, string age, ...}

'b is new {int productId, age string}

Так как оба типа 'b a new {int productId, age string} Я ожидал успеха; тем не менее, компилятор по-прежнему не работает. Я верю, что один 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 вчера во new {Key1 = gr.productId, Key2 = gr.age} равно y.productKey в external0

присоединяйтесь к y вчера во new {gr.productId, gr.age} равно y.productKey в external0

Опять же, исходный запрос, на котором выполняется эта модификация, успешно работает. Я почти уверен, что это одна из проблем "немного знаний, это опасная вещь". Или, может быть, просто «мало знаний». В любом случае, я надеюсь, что Боги LINQ смогут найти решение.

Ответы [ 2 ]

0 голосов
/ 31 декабря 2011

Следующие изменения, по-видимому, дали требуемый результат.

  1. В зависимых запросах группировка была изменена на new {p.id, p.ageId}
  2. Все предложения orderby были пересмотрены с одного заказа на основе g.Key до 2 отдельных предложений orderby на основе g.Key.id и g.Key.ageId
  3. Наконец, в запросе, определяющем таблицу Left , я использовал следующее:

group new {pr.code, pr.product, pr.uom} by 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 за то, что он потратил время на внесение предложений.Пожалуйста, дайте мне знать, если вы найдете способ улучшить это.

0 голосов
/ 30 декабря 2011

Попробуйте объявить именованный тип для ключа из нескольких столбцов вместо использования анонимного:

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

    public int ProductAge { get; set; }
}

Используйте этот ProductKey в предложениях «group by» и «join». Таким образом, ваши запросы будут выглядеть так:

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

UPDATE:

Предложение ORDER BY с классом ProductKey выдаст ошибку (linq не знает, как упорядочить класс с несколькими столбцами), поэтому вам следует заказывать именно по g.Key.ProductId

...