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

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

Related

How to join with or condition? Cross Join is creating a IS NULL where clause

Basically I want to create an inner or join. In SQL:
SELECT COUNT(*) FROM Discounts
JOIN DiscountArticles ON Discounts.ID = DiscountArticles.Discount
JOIN Articles ON (DiscountArticles.Article = Articles.ID OR DiscountArticles.ArticleGroup = Articles.ArticleGroup)
This answer says, it's not possible using entity framework, however we can use an cross join, which works fine on SQL:
SELECT COUNT(*) AS GroupCount FROM Discounts
JOIN DiscountArticles ON Discounts.ID = DiscountArticles.Discount
CROSS JOIN Articles
WHERE (Articles.ID = DiscountArticles.Article OR Articles.ArticleGroup = DiscountArticles.ArticleGroup)
So I tried this:
var query = from discounts in _dbContext.Discounts
join discountArticles in _dbContext.DiscountArticles on discounts.Id equals discountArticles.Discount
from article in _dbContext.Articles
where article.Id == discountArticles.Article || article.ArticleGroup.Value == discountArticles.ArticleGroup.Value
select new
{
ArticleId = article.Id,
DiscountId = discounts.Id
};
However, this resolves into this this SQL query:
SELECT [a].[ID] AS [ArticleId], [d].[ID] AS [DiscountId]
FROM [Discounts] AS [d]
INNER JOIN [DiscountArticles] AS [d0] ON [d].[ID] = [d0].[Discount]
CROSS JOIN [Articles] AS [a]
WHERE ([a].[ID] = [d0].[Article]) OR (([a].[ArticleGroup] = [d0].[ArticleGroup]) OR (([a].[ArticleGroup] IS NULL) AND ([d0].[ArticleGroup] IS NULL)))
As you can see, there is an addtional check OR (([a].[ArticleGroup] IS NULL) AND ([d0].[ArticleGroup] IS NULL)), which is causing to return 6 time more results.
ArticleGroup is Nullable Guid? on both entities, so I guess it has something to do with it.
If I additional check it for null, I get the correct results:
where article.Id == discountArticles.Article ||
article.ArticleGroup.HasValue && article.ArticleGroup == discountArticles.ArticleGroup
However, I also get an bigger where clause in SQL:
WHERE ([a].[ID] = [d0].[Article]) OR (([a].[ArticleGroup] IS NOT NULL) AND ([a].[ArticleGroup] = [d0].[ArticleGroup]))
Is it somehow possible to generate a Query, which is more like my second SQL example? Something like this:
WHERE (Articles.ID = DiscountArticles.Article OR Articles.ArticleGroup = DiscountArticles.ArticleGroup)
Try this query, it will create appropriate join. I do not think that you need CROSS JOIN here.
var query =
from discounts in _dbContext.Discounts
join discountArticles in _dbContext.DiscountArticles on discounts.Id equals discountArticles.Discount
from article in _dbContext.Articles
.Where(article => article.Id == discountArticles.Article || article.ArticleGroup.Value == discountArticles.ArticleGroup.Value)
select new
{
ArticleId = article.Id,
DiscountId = discounts.Id
};
According to null comparison, check this option Using relational null semantics. Bad here that it will affect all queries:
services.AddDbContext<MyDbContext>(options =>
{
options.UseSqlServer(sourceConnection, sqlOptions =>
{
sqlOptions.UseRelationalNulls();
});
});

Left Join, Where and GroupBY clause using LINQ C#

I am trying to convert the following query into a LINQ statement.
select u.code, u.id, count(d.unitid) from Unit u
Left join Package d on u.id= d.unitid and d.sstatus = 'Open'
where d.hunit is not null
group by u.code, u.id
This is what I have,
var result =
from U in Table<Unit>().ToList()
join D in Table<Package>().Where(x => x.status == "Open").ToList() on U.Id equals D.UnitId into def1
from def2 in def1.DefaultIfEmpty()
group def2 by new
{
U.Id,
U.code
} into grouped
select new
{
Hmy = grouped.Key.Id,
Code = grouped.Key.Code,
TotalPAckages = grouped.Count()
};
But, in above code, I am also getting the results with UnitId is null in Package table.
I am not sure where and how to apply the Where clause part (where d.hunit is not null) in the above LINQ statement.

Select IN LINQ to entities with inner joins

I have the follow SQL query
SELECT ob.PK_OBJETIVO,
ev.NM_EVENTO,
ifi.QT_NOTA_IMPACTO,
imi.QT_NOTA_IMPACTO,
ire.QT_NOTA_IMPACTO,
(ifi.QT_NOTA_IMPACTO + imi.QT_NOTA_IMPACTO + ire.QT_NOTA_IMPACTO)/3 AS
Media
FROM AVALIACAO_IMPACTO AS ai
INNER JOIN EVENTO AS EV ON ev.PK_EVENTO = ai.FK_AVALIACAO_IMPACTO_EVENTO
INNER JOIN OBJETIVO AS ob ON ob.PK_OBJETIVO =
ai.FK_AVALIACAO_IMPACTO_OBJETIVO
INNER JOIN IMPACTO_FINANCEIRO AS ifi ON ifi.PK_IMPACTO_FINANCEIRO =
ai.FK_AVALIACAO_IMPACTO_IMPACTO1
INNER JOIN IMPACTO_MISSAO AS imi ON IMI.PK_IMPACTO_MISSAO =
AI.FK_AVALIACAO_IMPACTO_IMPACTO2
INNER JOIN IMPACTO_REPUTACAO AS IRE ON IRE.PK_IMPACTO_REPUTACAO =
AI.FK_AVALIACAO_IMPACTO_IMPACTO3
WHERE ai.FK_AVALIACAO_IMPACTO_OBJETIVO IN
(SELECT OBJ.PK_OBJETIVO
FROM OBJETIVO AS OBJ
WHERE OBJ.FK_OBJETIVO_PROCESSO = 3)
I need to transform that query in a LINQ query, I tried that:
var queryImpactos = await _context.AvaliacaoImpacto
.Where(e => _context.Objetivo.Select(o =>
o.ProcessoID).Contains(planoRiscos.Auditoria.ProcessoID))
.Include(e => e.Evento).Include(e => e.Objetivo)
.Include(e => e.ImpactoFinanceiro).Include(e =>
e.ImpactoMissao).Include(e => e.ImpactoReputacao).ToListAsync();
"planoRiscos.Auditoria.ProcessoID" returns what it takes, the number 3 of the pure SQL query, but the query result is returning all the records in the AVALIACAO_IMPACTO table, however I only need the records where a FK_OBJETIVO in AVALIACAO_IMPACTO exists within the OBJETIVO table, where the FK_PROCESSO in OBJETIVO is equal to the passed parameter (planoRiscos.Auditoria.ProcessoID).
Tip: whenever you need to convert SQL query to a LINQ query, make the query syntax your first choice.
Try this:
var queryImpactos = from ai in AVALIACAO_IMPACTO
join
ev in EVENTO on ai.FK_AVALIACAO_IMPACTO_EVENTO equals ev.PK_EVENTO
join ob in OBJETIVO on ai.FK_AVALIACAO_IMPACTO_OBJETIVO equals ob.PK_OBJETIVO
join ifi in IMPACTO_FINANCEIRO on ai.FK_AVALIACAO_IMPACTO_IMPACTO1 equals ifi.PK_IMPACTO_FINANCEIRO
join imi in IMPACTO_MISSAO on ai.FK_AVALIACAO_IMPACTO_IMPACTO2 equals imi.PK_IMPACTO_MISSAO
join IRE in IMPACTO_REPUTACAO on ai.FK_AVALIACAO_IMPACTO_IMPACTO3 equals IRE.PK_IMPACTO_REPUTACAO
where (from obj in OBJETIVO where obj.FK_OBJETIVO_PROCESSO == 3 select obj.PK_OBJETIVO).Contains(ai.FK_AVALIACAO_IMPACTO_OBJETIVO)
select new
{
ob.PK_OBJETIVO,
ev.NM_EVENTO,
QT_NOTA_IMPACTO1 = ifi.QT_NOTA_IMPACTO,
QT_NOTA_IMPACTO2 = imi.QT_NOTA_IMPACTO,
QT_NOTA_IMPACTO3 = IRE.QT_NOTA_IMPACTO,
Media = (ifi.QT_NOTA_IMPACTO + imi.QT_NOTA_IMPACTO + IRE.QT_NOTA_IMPACTO) / 3
};
Hope it's what you're looking for!

left join in Linq query

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.

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 };

Categories