2015-02-24 4 views
4

Я использую этот запрос в LINQPad. Он работает, за исключением того, что ProductSeries имеет дубликаты записей.LINQ Outer Join имеет дубликаты

var query = from etaRecord in EtaRecord_0140 

    join productSeriesRecord in ProductSeries 
    on etaRecord.ProductSeriesID equals productSeriesRecord.ProductSeriesID 
    into productSeriesGroup 
    from productSeries in productSeriesGroup.DefaultIfEmpty() 

    where etaRecord.State == "A" 
    select new { EtaRecord = etaRecord, ProductSeriesRecord = productSeries }; 

query.Dump(); 

Я попытался с помощью FirstOrDefault() вместо DefaultIfEmpty(), но я получаю эту ошибку:

An expression of type 'LINQPad.User.ProductSeries' is not allowed in a subsequent from clause in a query expression with source type 'System.Linq.IQueryable'. Type inference failed in the call to 'SelectMany'.

Как я могу получить FirstOrDefault() для ProductSeries так, что есть только одна строка для каждого EtaRecord?

.NET скрипку здесь: https://dotnetfiddle.net/kRrold

+3

левое внешнее объединение может возвращать дубликаты, показать, что вы ожидаете и то, что текущий вывод –

+0

В настоящее время запрос не содержит дубликатов, поэтому я не могу показать плохой результат. Проблема в том, что данные могут измениться, и тогда в будущем будут дубликаты. Мне нужно защититься от этого. Мне нужен только один ряд за etaRecord. И что etaRecord может иметь нулевой ProductSeries или только один ProductSeries, даже если их несколько. –

+0

Добавил ссылку .NET скрипта в мой пост. –

ответ

0

Вы говорите, что коллекция Вы присоединяетесь на Повторяющиеся? Потому что, если это так, вы можете просто группировать коллекцию ProductSeries раньше времени.

var query = from etaRecord in EtaRecord_0140 

join productSeriesRecord in ProductSeries.GroupBy(series => series.ProductSeriesID).Select(seriesGroup => seriesGroup.First()) 
on etaRecord.ProductSeriesID equals productSeriesRecord.ProductSeriesID 
into productSeriesGroup 
from productSeries in productSeriesGroup.DefaultIfEmpty() 

where etaRecord.State == "A" 
select new { EtaRecord = etaRecord, ProductSeriesRecord = etaRecord }; 

query.Dump(); 

Теперь предполагается, что вы работаете со статическим списком, а не с БД. Если это соединение с БД, вы должны, вероятно, сделать отчетливые результаты. Это можно сделать аналогичным образом после факта.

+0

Это именно то, что я говорю. Я просто попробовал ваш запрос, но теперь все строки имеют нулевой ProductSeriesRecord. –

+0

А, а если вы удалите DefaultIfEmpty? Похоже, что DefaultIfEmpty возвращает значение по умолчанию для типа, а его Nothing/Null для ссылочных типов – Tim

+0

Если я удалю 'DefaultIfEmpty()', тогда я получаю только строки, имеющие ProductSeries. Мне не хватает всех строк, которые этого не делают. –

1

Похоже, что вам нужно сгруппировать:

var query = from etaRecord in EtaRecord_0140 

join productSeriesRecord in ProductSeries 
on etaRecord.ProductSeriesID equals productSeriesRecord.ProductSeriesID 
into productSeriesGroup 
from productSeries in productSeriesGroup.DefaultIfEmpty() 

where etaRecord.State == "A" 
group productSeries by new { etaRecord.ProductSeriesId, etaRecord } into g 
select new 
     { 
     EtaRecord = g.Key.etaRecord, 
     ProductSeriesRecord = g.Select(x => x).FirstOrDefault() 
     }; 

UPDATED FIDDLE

+0

На вашей групповой линии мне пришлось изменить 'productSeriesGroup' на' productSeries'. Теперь я остался с этой ошибкой: 'Имя 'productSeriesRecord' не существует в текущем контексте' –

+0

@BobHorn см. Обновление –

+0

В строке группы:' Имя 'productSeriesRecord' не существует в текущем контексте' –

0

Я хотел бы сделать это в подзапрос:

var query = from etaRecord in EtaRecord_0140 
      where etaRecord.State == "A" 
      select new 
      { 
       EtaRecord = etaRecord, 
       ProductSeriesRecord = 
        (from productSeriesRecord in ProductSeries 
        where productSeriesRecord.ProductSeriesID == etaRecord.ProductSeriesID 
        select productSeriesRecord).FirstOrDefault() 
      }; 

В LINQ к объектам это может быть неэффективной работы, поскольку подзапрос выполняется для каждого etaRecord, но так как весь оператор переведен в SQL, оптимизатор запросов будет заботиться о оптимизированном исполнителе по плану.

Это история LINQ-сущностей.

LINQ к SQL всегда, кажется, производит п + 1 запросов для группы объединения (который join - into) в сочетании с FirstOrDefault(). Я пробовал несколько сценариев, но я не могу заставить его генерировать только один запрос. Единственное решение, которое я мог бы найти генерируя один запрос является:

var query = from etaRecord in EtaRecord_0140 
      where etaRecord.State == "A" 
      from productSeriesRecord in 
        ProductSeries 
         .Where(ps => ps.ProductSeriesID == etaRecord.ProductSeriesID) 
         .Take(1) 
         .DefaultIfEmpt() 
      select new { EtaRecord = etaRecord, ProductSeriesRecord = productSeries }; 

Так синтаксис присоединиться прекращается и в довольно надуманным образом первая запись ProductSeries принадлежности к EtaRecord опрашивается.

+0

Соединения лучше в производительности, что я слышал –

+0

@EhsanSajjad Не уверен, что это применимо здесь. Я просто добавлял несколько заметок о производительности. –

+0

Это, кажется, возвращает правильные результаты, но генерирует тысячи SQL-запросов, что я и пытался исправить изначально. Вместо того, чтобы выполнить 1 секунду для выполнения, это заняло 40 секунд. –

0

Проблема ваш дополнительный from пункт:

from productSeries in productSeriesGroup.DefaultIfEmpty() 

Вы должны угробить, что и просто использовать:

let productSeries = productSeriesGroup.FirstOrDefault() 

... или просто использовать productSeriesGroup.FirstOrDefault() в пункте select, как это:

var query = from etaRecord in etaRecords    
      join productSeriesRecord in productSeriesRecords 
      on etaRecord.ProductSeriesId equals productSeriesRecord.ProductSeriesId 
      into productSeriesGroup 
      select new { EtaRecord = etaRecord, 
         ProductSeriesRecord = productSeriesGroup.FirstOrDefault() }; 

С изменением результата в результате:

Snuh 1 - null 
Snuh 2 - null 
Snuh 3 - null 
Snuh 4 - Description A 
Snuh 5 - null 
Snuh 6 - Description B 

Я предполагаю, что это то, что вы хотели.

+0

Первоначально 'let' был в запросе, но он вызвал создание тысяч SQL-операторов. Позвольте мне дать вашему коду выстрел. Благодарю. –

+0

Три проблемы: все серии продуктов имеют нулевое значение, для выполнения этого потребовалось 47 секунд (запрос в моем сообщении занимает около одной секунды), и были созданы тысячи SQL-операторов. Интересно, было бы лучше/проще просто получить дубликаты, а затем удалить их? –

+0

@BobHorn: Ну, похоже, что ваше соединение может работать некорректно. (Это хорошо с вашим образцом, используя LINQ to Objects). Что произойдет, если вы просто используете простое внутреннее соединение? Сначала я бы справился с правильностью, а затем выработал эффективность. Обратите внимание, что 'FirstOrDefault()' может вызывать проблемы из-за отсутствия упорядочения ... вы можете попробовать указать явный порядок на 'productSeriesRecords' и посмотреть, помогает ли это. Как я уже сказал, сначала я бы разобрался в правильности. –

0

Вы должны быть в состоянии добавить дополнительный этап фильтрации для группы по EtaRecord и просто выбрать первую запись в каждой группе

т.е.

query = (from r in query 
     group r by r.EtaRecord.EtaId into results 
     select results.FirstOrDefault());