5

I'm trying to do a left join, not an inner join in a linq query. I have found answers related to using DefaultIfEmpty() however I can't seem to make it work. The following is the linq query:

from a in dc.Table1 
join e in dc.Table2 on a.Table1_id equals e.Table2_id
where a.Table1_id == id
orderby a.sort descending
group e by new
{
    a.Field1,
    a.Field2
} into ga
select new MyObject
{
    field1= ga.Key.Field1,
    field2= ga.Key.Field2,
    manySubObjects = (from g in ga select new SubObject{
                                                        fielda= g.fielda,
                                                        fieldb= g.fieldb
                                                        }).ToList()
}).ToList();

The query only gives me the rows from table 1 that have a corresponding record in table 2. I would like every record in table 1 populated into MyObject and a list of 0-n corresponding records listed in manySubObjects for each MyObject.

UPDATE: I tried the answer to the question that is a "possible duplicate", mentioned below. I now have the following code that does give me one record for each item in Table1 even if there is no Table2 record.

from a in dc.Table1 
join e in dc.Table2 on a.Table1_id equals e.Table2_id into j1
from j2 in j1.DefaultIfEmpty()
where a.Table1_id == id
orderby a.sort descending
group j2 by new
{
    a.Field1,
    a.Field2
} into ga
select new MyObject
{
    field1= ga.Key.Field1,
    field2= ga.Key.Field2,
    manySubObjects = (from g in ga select new SubObject{
                                                        fielda= g.fielda,
                                                        fieldb= g.fieldb
                                                        }).ToList()
}).ToList();

However, with this code, when there is no record in table2 I get "manySubObject" as a list with one "SubObject" in it with all null values for the properties of "SubObject". What I really want is "manySubObjects" to be null if there is no values in table2.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
kralco626
  • 8,456
  • 38
  • 112
  • 169
  • possible duplicate of [LINQ - Left Join, Group By, and Count](http://stackoverflow.com/questions/695506/linq-left-join-group-by-and-count) – Enigmativity Jan 28 '15 at 00:00
  • 1
    I should add when there is no value in table2 manySubObjects should be null. I tried in the question above and the result has all of the records, so that is an improvement, however when there is no record in table2 I get an instantiated SubObject with all null values. What I really want is "manySubObjects" to be null if there is no value in table2. – kralco626 Jan 28 '15 at 00:17
  • I think the ternary in my answer addresses your most recent comment about needing "manySubObjects" to be null. – Shaun Luttin Jan 28 '15 at 02:05
  • .Net Fiddle https://dotnetfiddle.net/bSRdNp – kralco626 Feb 07 '15 at 16:24

3 Answers3

3

In reply to your update, to create the null listing, you can do a ternary in your assignment of manySubObjects.

select new MyObject
{
    field1= ga.Key.Field1,
    field2= ga.Key.Field2,
    manySubObjects =
        (from g in ga select g).FirstOrDefaut() == null ? null : 
        (from g in ga select new SubObject {
           fielda= g.fielda,
           fieldb= g.fieldb
        }).ToList()
}).ToList();

In reply to your comments, the above works with Linq to Objects but NOT with Linq to SQL. Linq to SQL will complain that it, "Could not translate expression ... into SQL and could not treat as a local expression." That's because Linq cannot translate the custom new SubObject constructor into SQL. To do that, you have to write more code to support translation into SQL. See Custom Method in LINQ to SQL query and this article.

I think we've sufficiently answered your original question about left joins. Consider asking a new question about using custom methods/constructors in Linq to SQL queries.

Community
  • 1
  • 1
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • 1
    I tried this, however, I'm getting an error saying `Could not translate expression "ga.Select(g = > new SubObject(){fielda = g.fielda, fieldb = g.fieldb})" into SQL and could not treat as a local expression`. any idea what might cause this error? I can't find where my code deviates from yours... – kralco626 Feb 04 '15 at 00:03
  • @kralco626 It could be that you have a space between the equals sign and the angle bracket in the `= >` part of your code. – Shaun Luttin Feb 04 '15 at 02:54
  • I don't actually have a "= >" in my code at all. It must be compiling my code into that format? – kralco626 Feb 05 '15 at 23:26
  • On your answer, if I comment out the line `(from g in ga select g).FirstOrDefaut() == null ? null : ` the error goes away (obviously the query then doesn't function as I want). So something with the way that line is constructed it doesn't like... – kralco626 Feb 05 '15 at 23:28
  • @kralco626 Could please post a comment that links to a dotnetfiddle of your code. That way I can look at your code. It's easier that way. – Shaun Luttin Feb 06 '15 at 03:28
  • @kralco626 It works with Linq to objects (i.e. querying `List` instead of `DbSet`.) Linq will fail, though, when trying to translate your custom `SubObject` constructor into T-SQL. See the update to my answer. – Shaun Luttin Feb 08 '15 at 19:33
  • It is just interesting that I only get the failure when using the ternary expression. If I take that out SubObject doesn't create any problems. – kralco626 Feb 10 '15 at 23:34
  • @kralco626 Can you post a *working* dotNetFiddle. That will help to ensure that the problem is not associated with conversion to SQL, because you will have a tough time connecting to SQL through dotNetFiddle. – Shaun Luttin Feb 11 '15 at 23:31
3

I think the desired Result that you want can be given by using GroupJoin()

The code Below will produce a structure like so

Field1, Field2, List < SubObject > null if empty

Sample code

 var query = dc.Table1.Where(x => Table1_id == id).OrderBy(x => x.sort)
                .GroupJoin(dc.Table2, (table1 => table1.Table1_id), (table2 => table2.Table2_id),
                    (table1, table2) => new MyObject 
                                        { 
                                            field1 = table1.Field1, 
                                            field2 = table1.Field2, 
                                            manySubObjects = (table2.Count() > 0) 
                                                ? (from t in table2 select new SubObject { fielda = t.fielda, fieldb = t.fieldb}).ToList()
                                                : null 
                                        }).ToList();

Dotnetfiddle link

UPDATE

From your comment I saw this

ga.Select(g = > new SubObject(){fielda = g.fielda, fieldb = g.fieldb})

I think it should be (depends on how "ga" is built)

ga.Select(g => new SubObject {fielda = g.fielda, fieldb = g.fieldb})

Please update your question with the whole query, it will help solve the issue.

** UPDATE BIS **

 sentEmails = //ga.Count() < 1 ? null :
              //(from g in ga select g).FirstOrDefault() == null ? null :
             (from g in ga select new Email{
                  email_to = g.email_to,
                  email_from = g.email_from,
                  email_cc = g.email_cc,
                  email_bcc = g.email_bcc,
                  email_subject = g.email_subject,
                  email_body = g.email_body }).ToList()

Should be:

 sentEmails = //ga.Count() < 1 ? null :
             ((from g in ga select g).FirstOrDefault() == null) ? null :
             (from g in ga select new Email{
                  email_to = g.email_to,
                  email_from = g.email_from,
                  email_cc = g.email_cc,
                  email_bcc = g.email_bcc,
                  email_subject = g.email_subject,
                  email_body = g.email_body }).ToList()

Checks if the group has a First, if it doesn't the group doesn't have any records so the Action.Name for a Time Stamp has no emails to send. If the First isn't null the loop throw the group elements and create a list of Email,

Community
  • 1
  • 1
CheGueVerra
  • 7,849
  • 4
  • 37
  • 49
  • 1
    Thanks. The GroupJoin() does make for a tidier query. – Shaun Luttin Jan 28 '15 at 15:40
  • I'm getting the same `Could not translate expression "ga.Select(g = > new SubObject(){fielda = g.fielda, fieldb = g.fieldb})" into SQL and could not treat as a local expression` exception with your answer that I'm getting with Shaun's answer. I don't see what I'm doing differently than what you have... – kralco626 Feb 04 '15 at 00:30
  • Update your question with a Copy of your query – CheGueVerra Feb 04 '15 at 00:38
  • Why did you take out the check if the first element in the group was null or not ? If your Group doesn't have any items in it, how will it create an Email entity – CheGueVerra Feb 07 '15 at 17:08
0
var results =
(
    // Use from, from like so for the left join:
    from a in dc.Table1
    from e in dc.Table2
        // Join condition goes here
        .Where(a.Id == e.Id)
        // This is for the left join
        .DefaultIfEmpty()
    // Non-join conditions here
    where a.Id == id
    // Then group
    group by new
    {
        a.Field1,
        a.Field2
    }
).Select(g =>
    // Sort items within groups
    g.OrderBy(item => item.sortField)
    // Project required data only from each item
    .Select(item => new
    {
        item.FieldA,
        item.FieldB
    }))
// Bring into memory
.ToList();

Then project in-memory to your non-EF-model type.

Danny Varod
  • 17,324
  • 5
  • 69
  • 111