How to handle null values in linq expression with joins? [duplicate] - c#

This question already has answers here:
Sequence contains no elements?
(7 answers)
Closed 10 months ago.
I try to get some data from few table with joining. This query is give an error as System.InvalidOperationException , Message=" Sequence contains no elements ". How can I avoid this error.
var assignments = (from s in _db.SubmissionLinks
join a in _db.Assignments on s.AssignmentID equals a.AssignmentID
join p in _db.Projects on a.ProjectID equals p.ID
join s2 in _db.SystemUsers on p.SystemUserFK equals s2.ID
select new AssignmentViewModel()
{
SubmissionlinkID = s.SubmissionLinkID,
SubmissionlinkName = s.SubmissionName,
ProjectID = p.ID,
ProjectName = p.ProjectName,
Deadline = s.Deadline,
Userid = s2.ID,
Assignmentid = a.AssignmentID,
IsActive = s.ActiveStatus
}).Where(s =>s.Userid == ID && s.IsActive == 0).Distinct().First();
I try to avoid that by checking returning objects inside of the where clause but it didn't work
var assignments = (from s in _db.SubmissionLinks
join a in _db.Assignments on s.AssignmentID equals a.AssignmentID
join p in _db.Projects on a.ProjectID equals p.ID
join s2 in _db.SystemUsers on p.SystemUserFK equals s2.ID
select new AssignmentViewModel()
{
SubmissionlinkID = s.SubmissionLinkID,
SubmissionlinkName = s.SubmissionName,
ProjectID = p.ID,
ProjectName = p.ProjectName,
Deadline = s.Deadline,
Userid = s2.ID,
Assignmentid = a.AssignmentID,
IsActive = s.ActiveStatus
}).Where(s => s != null && s.Userid == ID && s.IsActive == 0).Distinct().First();

Agree with Panagiotis; its supposed to be that SubmissionLink has a .Assignment property that returns the parent Assignment, Assignment has a .Project prop that returns the Project etc, and then you just do:
_db.SubmissionLinks
.Where(s =>s.Userid == ID && s.IsActive == 0)
.Select(s => s.Select(s => new AssignmentViewModel()
{
SubmissionlinkID = s.SubmissionLinkID,
SubmissionlinkName = s.SubmissionName,
ProjectID = s.Assignment.Project.ID,
ProjectName = s.Assignment.Project.ProjectName,
Deadline = s.Deadline,
Userid = s.Assignment.Project.SystemUser.ID,
Assignmentid = s.Assignment.AssignmentID,
IsActive = s.ActiveStatus
})
.SomethingThatRunsTheQueryLike_First_FirstOrDefault_ToList_etc
EF works out the rest from how it seens you use the navigations (s.Assignment.Project.ProjectName etc). Starting from the many end and going up to the one end is quite easy, because it's just a single object at the end of the nav. Going down through collections is a bit more of a nuisance, so think carefully about where you start your query from for the easiest life. For example if you wanted the Project and its most recent submission, you might:
db.Projects.Select(p => new Something{
p.ProjectName,
MostRecentLinkId = p.SubmissionLinks.OrderByDescending(sl => sl.CreatedDate).First().Id
})
That p.SubmissionLinks.OrderByDescending(sl => sl.CreatedDate).First().Id would be translated (by recent EFC) to something like ... ROW_NUMBER() OVER(PARTITION BY projectid ORDER BY createddate DESC) rn ... WHERE rn=1 in a subquery, but working with collections is usually that little more tricky because you're performing aggregations to make them make sense in the context of a single parent. If you can start from a different place so youre not digging into collections it's simpler

Related

Entity Framework Core 2.2 orderby evaluated locally

I have this semi-complex query that counts the most voted user post of the last 7 days:
var fromDate = dateTimeService.Now.Add(-interval);
var votedPosts =
from vote in dbContext.Votes
join post in dbContext.Posts on vote.PostId equals post.PostId
group new {vote.Sign, post.PostId} by post.PostId
into postVotes
select new {
PostId = postVotes.Key,
TotalRating = postVotes.Sum(pv => pv.Sign)
};
var bestPost = (
from post in dbContext.Posts
join votedPost in votedPosts on post.PostId equals votedPost.PostId
join room in dbContext.Rooms on post.RoomId equals room.RoomId
join game in dbContext.Modules on room.ModuleId equals game.ModuleId
where room.RoomAccess == RoomAccessType.Open && post.CreateDate > fromDate
orderby votedPost.TotalRating descending,
post.CreateDate descending
select new BestPost
{
UserId = post.UserId,
ModuleId = game.ModuleId,
ModuleTitle = game.Title,
PostId = post.PostId,
PostText = post.Text,
PostCommentary = post.Commentary,
PostCreateDate = post.CreateDate,
TotalRating = bestPost.TotalRating
}).FirstOrDefault();
What I try to do here is to group user votes by PostId, sum the evaluations of their votes by field Sign (can be -1, 0 or 1), then join it with some additional data like game Id/Title and post texts, filter non-public or too old posts, then order it by rank and then by create date, then map it onto DTO and return the very first result if present.
All the fields here are simple basic types: the Vote.Sign is int, Post.CreateDate is DateTime, all the *Id are Guid and Text/Title/Commentary are string.
I get the warning:
warn: Microsoft.EntityFrameworkCore.Query[20500]
The LINQ expression 'orderby [bestPost].TotalRating desc' could not be translated and will be evaluated locally.
warn: Microsoft.EntityFrameworkCore.Query[20500]
The LINQ expression 'FirstOrDefault()' could not be translated and will be evaluated locally.
If I remove the sort by TotalRating and only leave the CreateDate sorting, it works fine, creates proper LIMIT request. But with TotalRating the query looks like this:
SELECT
t."PostId", t."TotalRating", post."CreateDate" AS "PostCreateDate",
post."UserId", game."ModuleId", game."Title" AS "ModuleTitle",
post."PostId" AS "PostId0", post."Text" AS "PostText",
post."Commentary" AS "PostCommentary"
FROM
"Posts" AS post
INNER JOIN
(SELECT
post0."PostId", SUM(vote."Sign")::INT AS "TotalRating"
FROM
"Votes" AS vote
INNER JOIN
"Posts" AS post0 ON vote."PostId" = post0."PostId"
GROUP BY
post0."PostId") AS t ON post."PostId" = t."PostId"
INNER JOIN
"Rooms" AS room ON post."RoomId" = room."RoomId"
INNER JOIN
"Modules" AS game ON room."ModuleId" = game."ModuleId"
WHERE
(room."RoomAccess" = 0) AND (post."CreateDate" > #__fromDate_0)
And it looks pretty bad to be calculated in dotnet runtime.
I tried to wrap the result in another select from, but it didn't help. I also cannot do the group by on all the columns because then I won't be able to aggregate things like ModuleId because EF Core 2.2 does not support the group.FirstOrDefault things and PostgreSQL does not support max(uuid) (otherwise I could use group.Max(g => g.ModuleId)).
What am I doing wrong?
What happens if you combine the gist of votedPosts into the query so you don't duplicate the join on posts?
var bestPost = (
from post in dbContext.Posts
join vote in dbContext.Votes on post.PostId equals vote.PostId into votej
let voteTotalRating = votej.Sum(pv => pv.Sign)
join room in dbContext.Rooms on post.RoomId equals room.RoomId
join game in dbContext.Modules on room.ModuleId equals game.ModuleId
where room.RoomAccess == RoomAccessType.Open && post.CreateDate > fromDate
orderby voteTotalRating descending,
post.CreateDate descending
select new BestPost {
UserId = post.UserId,
ModuleId = game.ModuleId,
ModuleTitle = game.Title,
PostId = post.PostId,
PostText = post.Text,
PostCommentary = post.Commentary,
PostCreateDate = post.CreateDate,
TotalRating = voteTotalRating
}).FirstOrDefault();

Entity LINQ query to slow

I'm new to the C#, I have a database that someone else designed, query works great, but compared with SQL, it's 10 times slower.
I made mistakes here for sure, anybody have tips to speed this up a little bit. This model is for displaying in table, and I converting int to ENUM and calculating discount for display.
Code is:
var results = from w in db.Washes.AsEnumerable()
join t in db.Wash_Types.AsEnumerable() on w.WashTypeId equals t.Id
join a in db.Accounts.AsEnumerable() on w.AccountId equals a.Id
orderby w.Id descending
select new AllWashesTable
{
Id = w.Id,
WashTime = w.WashTime,
WashTimeEnd = w.WashTimeEnd,
Name = a.Name,
Client = (w.Client != null ? w.Client.Naziv : ""),
MobileNumber = a.MobileNumber,
Identification = w.Identification,
WashType = WashTypeShowEnum.WashTypeShowEnumToString((WashTypeShowEnum.WashType) w.WashTypeId),
Price = int.Parse(t.WashPrice) * (1 - w.Discount) + "",
Discount = w.Discount
};
return results.ToList();
Seems all my entity queries are at least 5+ times slower than SQL. Somewhere I am making some mistake.
Your problem is the use of AsEnumerable. When the query gets executed (in your case the results.ToList()), Everything that appears after it, will be evaluated using linq-to-objects. It means that your joins will not be handled by DB. You will fetch all the records from the tables.
However, your function WashTypeShowEnum.WashTypeShowEnumToString will not be recognized by entity framework.
You might want to move the AsEnumerable to the end and then select the results.
var results = (from w in db.Washes
join t in db.Wash_Types on w.WashTypeId equals t.Id
join a in db.Accounts on w.AccountId equals a.Id
orderby w.Id descending
select new {w, a, t}).AsEnumerable().Select(arg=> new AllWashesTable
{
Id = arg.w.Id,
WashTime = arg.w.WashTime,
WashTimeEnd = arg.w.WashTimeEnd,
Name = arg.a.Name,
Client = (arg.w.Client != null ? arg.w.Client.Naziv : ""),
MobileNumber = arg.a.MobileNumber,
Identification = arg.w.Identification,
WashType = WashTypeShowEnum.WashTypeShowEnumToString((WashTypeShowEnum.WashType) arg.w.WashTypeId),
Price = int.Parse(arg.t.WashPrice) * (1 - arg.w.Discount) + "",
Discount = arg.w.Discount
};
return results.ToList();

LINQ to SQL - multiple left join, group by, left join on same table, count

I have a SQL query that is pretty simple, but turns out to be a nightmare to transform into LINQ to SQL (or LINQ to Entity). I've been reading lots of questions and articles and I just cannot manage to make it work. Here is the SQL query (Items table 1-N to Operations table, Items has a ParentItemId column):
select
i.Id
, i.Code
, count(oAdd.ItemId) "Added"
, count(oRem.ItemId) "Removed"
, count(iChild.Id) "Existing"
from items i
left join operations oAdd on oAdd.ItemId = i.Id and oAdd.OperationTypeId = 10
left join operations oRem on oRem.ItemId = i.Id and oRem.OperationTypeId = 20
left join items iChild on iChild.ParentItemId = i.Id
group by
i.Id
, i.Code
Now after all the reseach and multiple attemps, I came up with the following code, which compiles but throws a EntityCommandCompilation exception ("The nested query is not supported. Operation1='GroupBy' Operation2='MultiStreamNest'"):
var query =
from item in dbContext.Items
join oAdd in dbContext.Operations on item.Id equals oAdd.ItemId into oAddJoin
join oRem in dbContext.Operations on item.Id equals oRem.ItemId into oRemJoin
join oChild in dbContext.Items on item.Id equals oChild.ParentItemId into oChildJoin
from oAddLeftJoin in oAddJoin.Where(o => o.OperationTypeId == (int)OperationTypes.AddTo).DefaultIfEmpty()
from oRemLeftJoin in oRemJoin.Where(o => o.OperationTypeId == (int)OperationTypes.RemoveFrom).DefaultIfEmpty()
from oChildLeftJoin in oChildJoin.DefaultIfEmpty()
group oChildLeftJoin by new
{
ItemId = item.Id,
ItemCode = item.Code,
Added = oAddJoin.Count(),
Removed = oRemJoin.Count(),
Existing = oChildJoin.Count()
}
into oChildLeftJoinGrouped
select new
{
oChildLeftJoinGrouped.Key.ItemId,
oChildLeftJoinGrouped.Key.Added,
oChildLeftJoinGrouped.Key.Removed,
oChildLeftJoinGrouped.Key.Existing
};
var summaryList = query.ToList();
I've also tried not grouping, not "joining into" but selecting from where instead, not putting the count in the group by but in the select new, not grouping by added/removed/existing. Nothing works, and the more I try the less I feel I understand what this is about. It worked only once but without the "Existing" count (count of child items, i.e. join on the same table - see SQL query above).
Seems to me like this is an easy SQL query. Should I put that in a view instead? Is it even possible to achieve this with LINQ (if possible without subqueries)?
Thank you for your help!
EDIT 1
The code below works but if a parent has N children it will appear N times in the result. Because there is no group by.
var query =
from item in dbContext.Items
join oAdd in dbContext.Operations on item.Id equals oAdd.ItemId into oAddJoin
join oRem in dbContext.Operations on item.Id equals oRem.ItemId into oRemJoin
from oAddLeftJoin in oAddJoin.Where(o => o.OperationTypeId == (int)OperationTypes.AddTo).DefaultIfEmpty()
from oRemLeftJoin in oRemJoin.Where(o => o.OperationTypeId == (int)OperationTypes.RemoveFrom).DefaultIfEmpty()
let existingCount = dbContext.Items.Count(i => i.ParentItemId == item.Id)
select new
{
item,
Added = oAddJoin.Count(),
Removed = oAddJoin.Count(),
existingCount
};
var summaryList = query.ToList();
EDIT 2
Actually, below is the one which works and return good values. Even the SQL Query above is wrong. Sorry about the spam.
var query =
from item in dbContext.Items
let addedCount = dbContext.Operations.Count(o => o.ItemId == item.Id && o.OperationTypeId == (int)OperationTypes.AddTo)
let removedCount = dbContext.Operations.Count(o => o.ItemId == item.Id && o.OperationTypeId == (int)OperationTypes.RemoveFrom)
let existingCount = dbContext.Items.Count(i => i.ParentItemId == item.Id)
select new
{
item,
Added = addedCount,
Removed = removedCount,
Existing = existingCount
};
var summaryList = query.Distinct().ToList();
EDIT 3
The super ugly SQL query that works:
select distinct
i.Id
, i.Code
, (select count(*) from operations oAdded where oAdded.itemid = i.id and oAdded.operationtypeid = 10) "Added"
, (select count(*) from operations oAdded where oAdded.itemid = i.id and oAdded.operationtypeid = 20) "Removed"
, (select count(*) from items ic where ic.ParentItemId = i.id) "Existing"
from items i
If this is Entity Framework (you said LINQ to Entity), then you could make the operations and item tables navigation properties of item. Then, you could use a view model to calculate your counts. Something like:
public class ViewModel : Item
{
public int Added {get;set;}
public int Removed {get;set;}
public int Existing {get;set;}
public ViewModel(Item i) {
this.id = i.id;
this.code = i.code;
this.Added = i.operations.Where(o => o.operationTypeID == 10).Count;
this.Removed = i.operations.Where(o => o.operationTypeID == 20).Count;
this.Existing = i.Items.Count;
}
}
Then your query would just be:
dbContext.Items.Select(i => new ViewModel(i)).ToList();

Converting SQL to LINQ query when I cannot use "IN"

I'm trying to convert this very simple piece of SQL to LINQ:
select * from Projects p
inner join Documents d
on p.ProjectID = d.ProjectID
left join Revisions r
on r.DocumentID = d.DocumentID
and r.RevisionID IN (SELECT max(r2.RevisionID) FROM Revisions r2 GROUP BY r2.DocumentID)
WHERE p.ProjectID = 21 -- Query string in code
This says, if any revisions exist for a document, return me the highest revision ID. As it's a left join, if not revisions exist, I still want the results returned.
This works as expected, any revisions which exist are shown (and the highest revision ID is returned) and so are all documents without any revisions.
When trying to write this using LINQ, I only get results where revisions exist for a document.
Here is my attempt so far:
var query = from p in db.Projects
join d in db.Documents on new { ProjectID = p.ProjectID } equals new { ProjectID = Convert.ToInt32(d.ProjectID) }
join r in db.Revisions on new { DocumentID = d.DocumentID } equals new { DocumentID = Convert.ToInt32(r.DocumentID) } into r_join
from r in r_join.DefaultIfEmpty()
where
(from r2 in db.Revisions
group r2 by new { r2.DocumentID }
into g
select new { MaxRevisionID = g.Max(x => x.RevisionID) }).Contains(
new { MaxRevisionID = r.RevisionID }) &&
p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new { d.DocumentID, d.DocumentNumber, d.DocumentTitle, RevisionNumber = r.RevisionNumber ?? "<No rev>", Status = r.DocumentStatuse == null ? "<Not set>" : r.DocumentStatuse.Status };
I'm not very good at LINQ and have been using the converter "Linqer" to help me out, but when trying I get the following message:
"SQL cannot be converted to LINQ: Only "=" operator in JOIN expression
can be used. "IN" operator cannot be converted."
You'll see I have .DefaultIfEmpty() on the revisions table. If I remove the where ( piece of code which does the grouping, I get the desired results whether or not a revision exists for a document or not. But the where clause should return the highest revision number for a document IF there is a link, if not I still want to return all the other data. Unlike my SQL code, this doesn't happen. It only ever returns me data where there is a link to the revisions table.
I hope that makes a little bit of sense. The group by code is what is messing up my result set. Regardless if there is a link to the revisions table, I still want my results returned. Please help!
Thanks.
=======
The code I am now using thanks to Gert.
var query = from p in db.Projects
from d in p.Documents
where p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new
{
p.ProjectID,
d.DocumentNumber,
d.DocumentID,
d.DocumentTitle,
Status = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().DocumentStatuse.Status,
RevisionNumber = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().RevisionNumber
};
gvDocumentSelection.DataSource = query;
gvDocumentSelection.DataBind();
Although this works, you'll see I'm selecting two fields from the revisions table by running the same code, but selecting two different fields. I'm guessing there is a better, more efficient way to do this? Ideally I would like to join on the revisions table in case I need to access more fields, but then I'm left with the same grouping problem again.
Status = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().DocumentStatuse.Status,
RevisionNumber = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault().RevisionNumber
Final working code:
var query = from p in db.Projects
from d in p.Documents
where p.ProjectID == Convert.ToInt32(Request.QueryString["projectId"])
select new
{
p.ProjectID,
d.DocumentNumber,
d.DocumentID,
d.DocumentTitle,
LastRevision = d.Revisions
.OrderByDescending(rn => rn.RevisionID)
.FirstOrDefault()
};
var results = from x in query
select
new
{
x.ProjectID,
x.DocumentNumber,
x.DocumentID,
x.DocumentTitle,
x.LastRevision.RevisionNumber,
x.LastRevision.DocumentStatuse.Status
};
gvDocumentSelection.DataSource = results;
gvDocumentSelection.DataBind();
If you've got 1:n navigation properties there is a much simpler (and recommended) way to achieve this:
from p in db.Projects
from d in p.Documents
select new { p, d,
LastRevision = d.Revisions
.OrderByDescending(r => r.RevisionId)
.FirstOrDefault() }
Without navigation properties it is similar:
from p in db.Projects
join d in db.Documents on new { ProjectID = p.ProjectID }
equals new { ProjectID = Convert.ToInt32(d.ProjectID) }
select new { p, d,
LastRevision = db.Revisions
.Where(r => d.DocumentID = Convert.ToInt32(r.DocumentID))
.OrderByDescending(r => r.RevisionId)
.FirstOrDefault() }
Edit
You can amend this very wide base query with all kinds of projections, like:
from x in query select new { x.p.ProjectName,
x.d.DocumentName,
x.LastRevision.DocumentStatus.Status,
x.LastRevision.FieldA,
x.LastRevision.FieldB
}

Linq to Entities Left Outer Join with different types

I've probably spent 40 hours on this problem so far, I've tried every solution on this site and on google, and I still can't make this work.
I need to left join a table to the results of a previous query, stored in a var. The joining field is a varchar in the table queried for the result in the var, and a bigint (long) in the table being joined. Here is the current attempt, which tells me "Object reference not set to an instance of an object." All Entities errors seem like nonsense and lies to me, I assume it's trying to tell me nothing matched, but who knows.
List<reportUser> ru = leaders
.GroupJoin(db.sweeps,
a => a.FBID.ToString(),
s => s.userFBID.First().ToString(),
(a, matching) => new reportUser
{
FBID = a.FBID,
CurrentPoints = a.CurrentPoints,
Name = matching.FirstOrDefault().Name,
Email = matching.FirstOrDefault().email
}
?? new reportUser
{
FBID = 0,
CurrentPoints = 0,
Name = "",
Email = ""
})
.Select(a => a)
.ToList();
Here's the SQL requested below. I've included the SQL to build the Leaders object as well, all the above is really meant to represent is the last line, which is simply a left join.
select s.name, s.email, b.score, c.score overall
from (
select a.userfbid, sum(a.pointvalue) score
from (
select userfbid, pointvalue
from l
left join qa on qa.id = l.qaid
left join q on q.id = qa.qid
left join qz on qz.id = q.qzid
where qa.pointvalue > 0 and qz.cid = 12
union all
select fbid userfbid, pointvalue
from bn
where date >= '5/9/2011 04:00' and
date <= '5/16/2011 04:00'
) a
group by a.userfbid
) b
left join (
select a.userfbid, sum(a.pointvalue) score
from (
select userfbid, pointvalue
from l
left join qa on qa.id = l.qaid
left join q on q.id = qa.qid
left join qz on qz.id = q.qzid
where qa.pointvalue > 0
union all
select fbid userfbid, pointvalue
from bn
) a
group by a.userfbid
) c on c.userfbid=b.userfbid
left join s on s.userfbid=b.userfbid
order by score desc
I'm assuming that in your database s.userFBID.First() is never null?
If that's right, then your problem could be in the FirstOrDefault().Name type statements - when FirstOrDefault() evaluates to null then obviously you will get a nullreferenceexception :/
To get around this, try something like:
List<reportUser> ru = leaders
.GroupJoin(db.sweeps,
a => a.FBID.ToString(),
s => s.userFBID.First().ToString(),
(a, matching) =>
{
var match = matching.FirstOrDefault();
return match != null ?
new reportUser
{
FBID = a.FBID,
CurrentPoints = a.CurrentPoints,
Name = match.Name,
Email = match.email
}
: new reportUser
{
FBID = 0, // a.FBID ?
CurrentPoints = 0, // a.CurrentPoints ?
Name = "",
Email = ""
}})
.Select(a => a)
.ToList();
However, I find it a bit hard to do this without seeing the structure of the database... or some sample data
Once you've got something working... then I highly recommend you try breaking this down into something more easily understandable - I'm really not sure what's going on here!
Here's a simple left outer join for you:
var query = from l leaders
join s in db.sweeps on l.FBID equals s.userFBID.First() into joined
from j in joined.FirstOrDefault()
select new reportUser
{
FBID = l.FBID,
CurrentPoints = l.CurrentPoints,
Name = j == null ? string.Empty : j.Name,
Email = j == null ? string.Empty : j.email
}
If this isn't quite what you are looking for... maybe try posting the SQL for what you actually want.

Categories

Resources