left join in Linq query - c#

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.

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();
Here is a dotnetfiddle that tries to do what you're attempting. https://dotnetfiddle.net/kGJVjE
Here is a subsequent dotnetfiddle based on your comments. https://dotnetfiddle.net/h2xd9O
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.

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,

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.

Related

How to add less than or equal to condition in linq inner join

We have two objects, Dates and ActiveEvents. Want to perform inner join on these with less than or equal to condition in linq. Same as ref of below SQL where consider #Tables are C# objects
Select A. from #Activities A
Inner Join #Dates D ON A.ActivityDate <= D.ProcessDate
Tried with below but it's not giving correct results.
var filteredActivity = (from e in ActiveEvents
from p in dates
where e.ActivityDate <= p.Date
select new ActiveEvent
{
ActivityDate = p.Date,
EventId = e.EventId
}).ToList();
And
var filteredActivity = (from e in ActiveEvents
from p in dates.Where(r => e.ActivityDate <= r)
select new ActiveEvent
{
ActivityDate = p.Date,
EventId = e.EventId
}).ToList();
Can you please suggest any better way to do this?
You can try this way
var filteredActivity = (from e in ActiveEvents
join p in dates
where e.ActivityDate <= p.ProcessDate
select new ActiveEvent
{
ActivityDate = p.Date,
EventId = e.EventId
}).ToList();
P/s: Ideally, between 2 tables should contain the foreign key to join like this join p in dates on e.Key equals p.ForeignKey
Based on your example, the query is filtering on ProcessDate but your linq query is filtering on p.Date. Are those the same field? The first example you gave should be correct.

Linq EF group by to get latest entries into list of objects

I am trying to move from simple SQL to EF.
But there are some complex queries(joins) that it seems to hard to generate the linq for.
At first I tried to use sqltolinq tool to generate the linq but it gives error as some of the things are not supported in the query.
here is the linq:
var entryPoint = (from ep in dbContext.tbl_EntryPoint
join e in dbContext.tbl_Entry on ep.EID equals e.EID
join t in dbContext.tbl_Title on e.TID equals t.TID
where e.OwnerID == user.UID
select new {
UID = e.OwnerID,
TID = e.TID,
Title = t.Title,
EID = e.EID
});
The table entry has many entries that I would like to group and get the latest for each group. But then I would need to select into a view model object which will be bind to gridview.
I dont know where I can implement the logic to group by and get the latest from each and be able to get values from join table into viewModel object.
somewhere I need to add
group entry by new
{
entry.aID,
entry.bCode,
entry.Date,
entry.FCode
}
into groups
select groups.OrderByDescending(p => p.ID).First()
in the above linq to retrieve latest from each group.
You can insert group by right after the joins:
var query =
from ep in dbContext.tbl_EntryPoint
join e in dbContext.tbl_Entry on ep.EID equals e.EID
join t in dbContext.tbl_Title on e.TID equals t.TID
where e.OwnerID == user.UID
group new { ep, e, t } by new { e.aID, e.bCode, e.Date, e.FCode } into g
let r = g.OrderByDescending(x => x.e.ID).FirstOrDefault()
select new
{
UID = r.e.OwnerID,
TID = r.e.TID,
Title = r.t.Title,
EID = r.e.EID
};
The trick here is to include what you need after the grouping between group and by.
However, the above will be translated to CROSS APPLY with all joins included twice. If the grouping key contains fields from just one table, it could be better to perform the grouping/selecting the last grouping element first, and then join the result with the rest:
var query =
from e in (from e in dbContext.tbl_Entry
where e.OwnerID == user.UID
group e by new { e.aID, e.bCode, e.Date, e.FCode } into g
select g.OrderByDescending(e => e.ID).FirstOrDefault())
join ep in dbContext.tbl_EntryPoint on e.EID equals ep.EID
join t in dbContext.tbl_Title on e.TID equals t.TID
select new
{
UID = e.OwnerID,
TID = e.TID,
Title = t.Title,
EID = e.EID
};

How to perform a left join with an additional filtering in LINQ to entities?

I have several tables, the main one is called DefectRecord, others are called DefectArea, DefectLevel...etc and the one called DefectAttachment. And this problem is about joining DefectRecord with other tables to get a ViewModel for further use. What the hard part I am facing is about the DefectAttachment table.
DefectRecord has a 1-to-many relation with DefectAttachment. While there may be NO attachment at all for one defect record, there may be multiple attachments.
Logically I tried to perform a left join among DefectRecord & DefectAttachment, but there is one more requiredment:
If there is multiple attachments, select ONLY the oldest one(i.e. the
one with oldest CreatedDate field value)
I am stuck at this requirement, how can I perform this with LINQ-to-Entities? Below is the code of what I have now:
var ret = (from dr in defectRecordQuery
join ft in filterQuery on dr.FilterID equals ft.FilterID
join l in levelQuery on dr.LevelID equals l.LevelID
join a in attachmentQuery on dr.DefectRecordID equals a.DefectRecordID into drd
from g in drd.DefaultIfEmpty()
select new DefectRecordViewModel
{
DefectRecordCode = dr.Code,
DefectAttachmentContent = g == null ? null : g.FileContent,
LookupFilterName = ft.FilterName,
}).ToList();
The *Query variable are the IQueryable object which get the full list of corresponding table.
Group your results by the Code and FilterName and then for the content take that of the item in the group that has the oldest date
var ret = (from dr in defectRecordQuery
join ft in filterQuery on dr.FilterID equals ft.FilterID
join l in levelQuery on dr.LevelID equals l.LevelID
join d in attachmentQuery on dr.DefectRecordID equals d.DefectRecordID into drd
from g in drd.DefaultIfEmpty()
group g by new { dr.Code, ft.FilterName } into gg
select new DefectRecordViewModel
{
DefectRecordCode = gg.Key.Code,
DefectAttachmentContent = gg.OrderByDescending(x => x.CreateDateTime).FirstOrDefault() == null? null: gg.OrderByDescending(x => x.CreateDateTime).FirstOrDefault().FileContent,
LookupFilterName = gg.Key.FilterName,
}).ToList();
If using C# 6.0 or higher then you can do:
DefectAttachmentContent = gg.OrderByDescending(x => x.CreateDateTime)
.FirstOrDefault()?.FileContent,

How do I write an Entity Framework 4 query for the following SQL?

I have an Entity Framework 4 model in my application. I need it to generate the following SQL:
SELECT *
FROM Reads AS R
INNER JOIN Images AS I ON R.ReadId = I.ReadId AND I.ImageTypeId = 2
LEFT OUTER JOIN Alarms AS A ON R.ReadId = A.ReadId AND A.DomainId IN (103,102,101,2,1,12) AND A.i_active = 1
LEFT OUTER JOIN ListDetails AS L ON L.ListDetailId = A.ListDetailId
WHERE R.i_active = 1
Here's what I have now for this query in my code:
var items = from read in query
join plate in context.Images on read.ReadId equals plate.ReadId
join temp1 in context.Alarms on read.ReadId equals temp1.ReadId into alarms from alarm in alarms.DefaultIfEmpty()
join temp2 in context.ListDetails on alarm.ListDetailId equals temp2.ListDetailId into entries from entry in entries.DefaultIfEmpty()
where plate.ImageTypeId == 2 && plate.IActive == 1
select new { read, plate, alarm, entry.OfficerNotes };
How do I modify the Entity Framework query to get the SQL query I need?
For Left Joins - I suggest using a from with a where instead of join, I find it makes it cleaner
For Inner Joins with multiple join conditions - you need to join on two matching anonymous types, or use a from with where conditions
For x IN list, you need to specify your list outside the query and use the Any method or Contains method
List<int> domainIds = new List<int>() { 103,102,101,2,1,12 };
var items = from read in query
join plate in context.Images
on new { read.ReadId, ImageTypeId = 2 } equals new { plate.ReadId, plate.ImageTypeId }
from alarm in context.Alarms
.Where(x => x.IActive == 1)
.Where(x => domainIds.Any(y => y == x.DomainId))
.Where(x => read.ReadId == x.ReadId)
.DefaultIfEmpty()
from entry in context.ListDetails
.Where(x => alarm.ListDetailId == x.ListDetailId)
.DefaultIfEmpty()
select new { read, plate, alarm, entry.OfficerNotes };

LINQ to Entities - Multiple Joins - Null Reference Exception on 'Select'

I am trying to convert a SQL query to a LINQ to entities query, but am having some problems with the LINQ select block.
Here is the SQL query which performs as expected:
SELECT distinct( p.PendingID,
p.Description,
p.Date,
f.Status,
u.UserName,
m.MapID
FROM Pending p
JOIN Users u
ON p.UserID = u.UserID
LEFT JOIN Forks f
ON p.PendingID = f.PendingID
LEFT JOIN Maps m
ON f.ForkID = m.ForkID
ORDER BY p.Date DESC
Here is the LINQ to entities query as I have it thus far:
var pList = (from pending in pendingItems
// JOIN
from user in userList.Where(u => pending.UserID == u.UserID)
// LEFT OUTER JOIN
from fork in forkList.Where(f => pending.ID == f.PendingID)
.DefaultIfEmpty()
// LEFT OUTER JOIN
from map in mapList.Where(m => fork.ID == m.ForkID)
.DefaultIfEmpty()
orderby pending.Date descending
select new
{
ItemID = pending.ID, // Guid
Description = pending.Description, // String
Date = pending.Date, // DateTime
Status = fork.Status, // Int32 (*ERROR HERE*)
UserName = user.UserName, // String
MapID = map.ID // Guid (*ERROR HERE*)
})
.Distinct()
.ToList();
The LINQ query fails on either of the following 2 lines, which attempt to assign values retrieved from left outer join results. If the following lines are omitted, the LINQ query completes without errors:
Status = fork.Status,
MapID = map.ID
Why are those 2 property assignments failing within the LINQ query's select block?
The problem is that due to your outer joins, fork and map may be null. Of course when they're null, you can't access their properties. You may need something like this:
Status = (fork == null) ? null : fork.Status,
MapID = (map == null) ? null : map.ID

Categories