This is definitely a challenge. It's ugly, but It does the trick. It returns exactly what you are looking for. In the case of employees without attendance, you get back the name with null times.
var selectedDate = new DateTime(2009,4,28);
var query = from e in db.Employees
join a in db.Attendances on e.Id equals a.EmployeeId into Temp
from t in Temp.DefaultIfEmpty()
where t.SignInTime == null || (t.SignInTime >= selectedDate && t.SignInTime < selectedDate.AddDays(1))
group t by e.EmployeeName into grouped
select new
{
Employee = grouped.Key,
FirstSignInTime = grouped.Min(a => a.SignInTime),
LastSignOutTime = grouped.Max(a => a.SignOutTime)
};
This is the SQL that is emitted by that expression:
DECLARE @p0 DateTime = '2009-04-27 00:00:00.000'
DECLARE @p1 DateTime = '2009-04-28 00:00:00.000'
SELECT MIN([t1].[SignInTime]) AS [FirstSignInTime], MAX([t1].[SignOutTime]) AS [LastSignOutTime], [t0].[EmployeeName] AS [Employee]
FROM [Employee] AS [t0]
LEFT OUTER JOIN [Attendance] AS [t1] ON [t0].[Id] = [t1].[EmployeeId]
WHERE ([t1].[SignInTime] IS NULL) OR (([t1].[SignInTime] >= @p0) AND ([t1].[SignInTime] < @p1))
GROUP BY [t0].[EmployeeName]
This is very close to your original SQL. I added where "t.SignInTime == null" so that it returns employees with attendance, but you can remove that if that's not what you want.