tags:

views:

132

answers:

3

I've created two entities (simplified) in C#:

class Log {  
    entries = new List<Entry>();

    DateTime Date { get; set; }  
    IList<Entry> entries { get; set; }  
}  

class Entry {  
    DateTime ClockIn { get; set; }  
    DateTime ClockOut { get; set; }  
}  

I am using the following code to initialize the objects:

Log log1 = new Log() {
    Date = new DateTime(2010, 1, 1),                
};
log1.Entries.Add(new Entry() {
    ClockIn = new DateTime(0001, 1, 1, 9, 0, 0),
    ClockOut = new DateTime(0001, 1, 1, 12, 0, 0)
});

Log log2 = new Log()
{
    Date = new DateTime(2010, 2, 1),
};

The method below is used to get the date logs:

var query =  
    from l in DB.GetLogs()
    from e in l.Entries  
    orderby l.Date ascending  
    select new  
    {  
        Date = l.Date,  
        ClockIn = e.ClockIn,  
        ClockOut = e.ClockOut,  
    };  

The result of the above LINQ query is:

/*
 Date        | Clock In   | Clock Out
 01/01/2010  | 09:00      | 12:00    
*/

My question is, what is the best way to rewrite the LINQ query above to include the results from the second object I created (Log2), since it has an empty list. In the other words, I would like to display all dates even if they don't have time values.

The expected result would be:

/*
 Date        | Clock In   | Clock Out
 01/01/2010  | 09:00      | 12:00    
 02/01/2010  |            |            
*/
+10  A: 

Try this:

var query =  
    from l in DB.GetLogs()
    from e in l.Entries.DefaultIfEmpty()
    orderby l.Date ascending  
    select new  
    {  
        Date = l.Date,  
        ClockIn = e == null ? null : e.ClockIn,  
        ClockOut = e == null ? null : e.ClockOut,  
    };

See the docs for DefaultIfEmpty for more information on it.

EDIT: You might want to just change it to perform the final part in memory:

var dbQuery =  
    from l in DB.GetLogs()
    from e in l.Entries.DefaultIfEmpty()
    orderby l.Date ascending  
    select new { Date = l.Date, Entry = e };

var query = dbQuery.AsEnumerable()
                   .Select(x => new { 
                        Date = x.Date,
                        ClockIn = x.Entry == null ? null : x.Entry.CLockIn,
                        ClockOut = x.Entry == null ? null : x.Entry.CLockOut
                    });
Jon Skeet
Jon, I get the following with your example: Cannot assign <null> to anonymous type property
REA_ANDREW
Relating to the first attempt with ClockIn
REA_ANDREW
It seems to make this example work you must cast to a nullable type, and in this case I have posted an example casting to (DateTime?) . I did not know you could not assign null to an anonymous type property.
REA_ANDREW
Ah - I hadn't spotted the types involved. But yes, you can assign null to values in anonymous types.
Jon Skeet
thanks by the answer, applying your solution and the nullable type casts described by REA_ANDREW, I could get it to work perfectly!
Douglas H. M.
A: 

You should have a look at the LINQ Union Operator.

http://srtsolutions.com/public/blog/251070

Andrew Lewis
+2  A: 

This is building on top of Jon's solution. Using it I get the following error:

Cannot assign to anonymous type property

I have updated Jon's example to the following and it appears to give the desired result:

var logs = new []{log1,log2};

var query =  
from l in logs.DefaultIfEmpty()
from e in l.entries.DefaultIfEmpty()  
orderby l.Date ascending  
select new  
{  
    Date = l.Date,  
    ClockIn = e == null ? (DateTime?)null : e.ClockIn,  
    ClockOut = e == null ? (DateTime?)null : e.ClockOut,  
};  

query.Dump();

Andrew

P.S. the .Dump() is due to me using LINQ Pad.

REA_ANDREW
thanks by the answer, it worked perfectly!
Douglas H. M.