tags:

views:

1728

answers:

3

I have two objects, let's call them Input and Output

Input has properties Input_ID, Label, and Input_Amt
Output has properties Output_ID and Output_Amt

I want to perform the equivalent SQL statement in LINQ:

SELECT Label, Sum(Added_Amount) as Amount FROM
    (SELECT I.Label, I.Input_Amt + ISNULL(O.Output_Amt, 0) as Added_Amount
    FROM Input I LEFT OUTER JOIN Output O ON I.Input_ID = O.Output_ID)
GROUP BY Label

For the inner query, I'm writing something like:

var InnerQuery = from i in input
                 join o in output
                 on i.Input_ID equals o.Output_ID into joined
                 from leftjoin in joined.DefaultIfEmpty()
                 select new
                 {
                     Label = i.Label,
                     AddedAmount = (i.Input_Amt + leftjoin.Output_Amt)
                 };

In testing, however, the statement returns null. What gives?

Also, how can I continue the desired query and perform the group after I've added my amounts together, all within a single LINQ statement?

+1  A: 

Which LINQ provider is this actually using? Are you actually talking to a database, or just working in-process? If you're using LINQ to SQL, you can turn the log on to see what SQL is being generated.

I'm sure that InnerQuery itself won't be null - how are you examining the output?

Jon Skeet
I'm working with objects in a process. After the statement I'm doing a quick test: foreach (var v in InnerQuery) { string s = v.Label; decimal d = v.AddedAmount; } One iteration with a successful join is correct. Another iteration with no Output on the join throws NullReferenceException.
Erick B
Okay, so it's just LINQ to Objects. Well, you've got a left join (effectively) so you should *expect* that leftjoin might be null.
Jon Skeet
+1  A: 

Okay, now that I understand what's going on a bit better, the main problem is that you haven't got the equivalent of the ISNULL bit. Try this instead:

var InnerQuery = from i in input
                 join o in output
                 on i.Input_ID equals o.Output_ID into joined
                 from leftjoin in joined.DefaultIfEmpty()
                 select new
                 {
                     Label = i.Label,
                     AddedAmount = (i.Input_Amt + (leftjoin == null ? 0 : leftjoin.Output_Amt))
                 };
Jon Skeet
So far so good! I assumed DefaultIfEmpty would take care of joins all by itself.Now how can I merge the Inner and Outer queries into one?var OuterQuery = from iq in InnerQuery group iq by iq.Label into grouping select new { Label = grouping.Key, Amount = grouping.Sum(item => item.AddedAmount) };
Erick B
DefaultIfEmpty just turns an empty sequence into one with a single "default" element (null in this case) in.
Jon Skeet
That nested query sounds about right. Does it work?
Jon Skeet
Yes, both queries work. Thanks!I'm simply curious to know if the outer and inner queries can be combined, since I'm still learning LINQ.
Erick B
Well you could just put brackets round everything in the RHS of InnerQuery and put that in the declaration of OuterQuery instead... but it would be very hard to understand.
Jon Skeet
This doesn't regroup by label.
David B
@David: It's not meant to - it represents the *inner* part of the SQL, which doesn't group either.
Jon Skeet
A: 
var labelsAndAmounts = input
  .GroupJoin
  (
    output,
    i => i.InputId,
    o => o.OutputId,
    (i, os) => new
    {
      i,
      oAmount = os.Any() ? os.Select(o => o.OutputAmt).Sum() : 0
    }
  )
  .GroupBy(x => x.i.Label)
  .Select(g => new
    {
      Label = g.Key,
      Amount = g.Select(x => x.i.InputAmt + x.oAmount).Sum()
    }
  );
David B