2009-08-15 2 views
1

Мне нужен такой простой, простой запрос, он удаляет меня, сколько работы я сделал, просто пытаясь сделать это в LINQ. В T-SQL, это было бы:LINQ aggregate left join on SQL CE

SELECT I.InvoiceID, I.CustomerID, I.Amount AS AmountInvoiced, 
     I.Date AS InvoiceDate, ISNULL(SUM(P.Amount), 0) AS AmountPaid, 
     I.Amount - ISNULL(SUM(P.Amount), 0) AS AmountDue 
FROM Invoices I 
LEFT JOIN Payments P ON I.InvoiceID = P.InvoiceID 
WHERE I.Date between @start and @end 
GROUP BY I.InvoiceID, I.CustomerID, I.Amount, I.Date 
ORDER BY AmountDue DESC 

Лучшее выражение эквивалентно LINQ Я придумал, взял меня гораздо больше времени, чтобы сделать:

var invoices = (
    from I in Invoices 
    where I.Date >= start && 
      I.Date <= end 
    join P in Payments on I.InvoiceID equals P.InvoiceID into payments 
    select new{ 
     I.InvoiceID, I.CustomerID, AmountInvoiced = I.Amount, InvoiceDate = I.Date, 
     AmountPaid = ((decimal?)payments.Select(P=>P.Amount).Sum()).GetValueOrDefault(), 
     AmountDue = I.Amount - ((decimal?)payments.Select(P=>P.Amount).Sum()).GetValueOrDefault() 
    } 
).OrderByDescending(row=>row.AmountDue); 

Это получает эквивалентный набор результатов при запуске против SQL Server. Однако использование базы данных SQL CE изменяет ситуацию. T-SQL остается почти таким же. Мне нужно только изменить ISNULL на COALESCE. Используя те же выражения LINQ, однако, приводит к ошибке:

There was an error parsing the query. [ Token line number = 4, 
Token line offset = 9,Token in error = SELECT ]

Так мы посмотрим на сгенерированный код SQL:

SELECT [t3].[InvoiceID], [t3].[CustomerID], [t3].[Amount] AS [AmountInvoiced], [t3].[Date] AS [InvoiceDate], [t3].[value] AS [AmountPaid], [t3].[value2] AS [AmountDue] 
FROM (
    SELECT [t0].[InvoiceID], [t0].[CustomerID], [t0].[Amount], [t0].[Date], COALESCE((
     SELECT SUM([t1].[Amount]) 
     FROM [Payments] AS [t1] 
     WHERE [t0].[InvoiceID] = [t1].[InvoiceID] 
     ),0) AS [value], [t0].[Amount] - (COALESCE((
     SELECT SUM([t2].[Amount]) 
     FROM [Payments] AS [t2] 
     WHERE [t0].[InvoiceID] = [t2].[InvoiceID] 
     ),0)) AS [value2] 
    FROM [Invoices] AS [t0] 
    ) AS [t3] 
WHERE ([t3].[Date] >= @p0) AND ([t3].[Date] <= @p1) 
ORDER BY [t3].[value2] DESC 

тьфу! Хорошо, так что он уродлив и неэффективен при работе с SQL Server, но мы не должны заботиться о нем, так как это Предполагалось, что будет быстрее напишите, а разница в производительности не должна быть такой большой. Но это просто не работает против SQL CE, который, по-видимому, не поддерживает подзапросы в списке SELECT.

На самом деле, я пробовал несколько разных запросов левого соединения в LINQ, и все они, похоже, имеют одинаковую проблему. Даже:

from I in Invoices 
join P in Payments on I.InvoiceID equals P.InvoiceID into payments 
select new{I, payments} 

генерирует:

SELECT [t0].[InvoiceID], [t0].[CustomerID], [t0].[Amount], [t0].[Date], [t1].[InvoiceID] AS [InvoiceID2], [t1].[Amount] AS [Amount2], [t1].[Date] AS [Date2], (
    SELECT COUNT(*) 
    FROM [Payments] AS [t2] 
    WHERE [t0].[InvoiceID] = [t2].[InvoiceID] 
    ) AS [value] 
FROM [Invoices] AS [t0] 
LEFT OUTER JOIN [Payments] AS [t1] ON [t0].[InvoiceID] = [t1].[InvoiceID] 
ORDER BY [t0].[InvoiceID] 

, который также приводит к ошибке:

There was an error parsing the query. [ Token line number = 2, 
Token line offset = 5,Token in error = SELECT ]

Так как я могу сделать простой левый присоединиться в базе данных SQL CE с помощью LINQ? Я трачу свое время?

ответ

3

Вы пробовали выражение запроса с group by, ближе к вашей версии T-SQL?

var invoices = 
    from I in Invoices 
    where I.Date >= start && I.Date <= end 
    join P in Payments on I.InvoiceID equals P.InvoiceID into J 
    group J.Sum(p => p.Amount) by new { I.InvoiceID, I.CustomerID, I.Amount, I.Date } into G 
    let AmountPaid = G.Sum() 
    let AmountDue = G.Key.Amount - AmountPaid 
    orderby AmountDue descending 
    select new 
    { 
     G.Key.InvoiceID, 
     G.Key.CustomerID, 
     AmountInvoiced = G.Key.Amount, 
     InvoiceDate = G.Key.Date, 
     AmountPaid, 
     AmountDue 
    }; 

Результат выглядит прямо против в памяти коллекций:

var Invoices = new[] { 
    new { InvoiceID = 1, CustomerID = 2, Amount = 2.5m, Date = DateTime.Today }, 
    new { InvoiceID = 2, CustomerID = 3, Amount = 5.5m, Date = DateTime.Today } 
}.AsQueryable(); 
var Payments = new[] { 
    new { InvoiceID = 1, Amount = 1m } 
}.AsQueryable(); 

Урожайность:

{ InvoiceID = 2, CustomerID = 3, AmountInvoiced = 5.5, InvoiceDate = 8/15/2009, 
    AmountPaid = 0, AmountDue = 5.5 } 
{ InvoiceID = 1, CustomerID = 2, AmountInvoiced = 2.5, InvoiceDate = 8/15/2009, 
    AmountPaid = 1, AmountDue = 1.5 } 

Если это не работа, LINQ покинул присоединиться обычно использует DefaultIfEmpty() на объединение результат. Возможно, вам придется сделать что-то вроде этого:

var invoices = 
    from I in Invoices 
    where I.Date >= start && I.Date <= end 
    join P in Payments on I.InvoiceID equals P.InvoiceID into J 
    from PJ in J.DefaultIfEmpty() // Left Join 
    group PJ by new { I.InvoiceID, I.CustomerID, I.Amount, I.Date } into G 
    let AmountPaid = G.Sum(p => p == null ? 0 : p.Amount) 
    // etc... 
+0

Ваше первое решение без 'DefaultIfEmpty()' взорвано аналогичной ошибкой, как и мои примеры. Ваш второй пример работает! Спасибо! Я попробую эту технику и в других моих попытках присоединиться. –