I have an SQL query which i am converting to Linq and i want to use Left Join instead of Inner Join.
I have tried DefaultIfEmpty() method but i haven't had any luck.
The Sql query:
SELECT t0.*, t1.* FROM entity AS t0
LEFT JOIN migration_logs AS t1 ON (CAST(t0.id AS CHAR) = t1.ObjectId and 'SASParty' = t1.ObjectType)
where t1.status is null || t1.Status <> '1' ORDER BY t0.id LIMIT 0, 10;
The Linq Query:
Entities
.Join(Migration_logs,
e => new { id = e.Id.ToString(), ObjectType = "SASParty" },
mlog => new { id = mlog.ObjectId, mlog.ObjectType },
(e, mlog) => new {e,mlog})
.Where(result => result.mlog.Status == null || result.mlog.Status != "1").DefaultIfEmpty().ToList()
I am using linqpad and when i execute the linq query it generates the following sql query:
SELECT t0.*
FROM entity AS t0
INNER JOIN migration_logs AS t1
ON ((CAST(t0.id AS CHAR) = t1.ObjectId) AND (#p0 = t1.ObjectType))
WHERE ((t1.Status IS NULL) OR (t1.Status <> #p1))
Some minor differences in the original query and generated sql query are there but i hope the problem statement is clear.
Any help would be appreciated.
I was able to find a solution with the linq to sql query and using into clause.
(from e in Entities
join mlog in Migration_logs
on new { id = e.Id.ToString(), ObjectType = "SASParty" }
equals new { id = mlog.ObjectId, mlog.ObjectType }
into results
from r in results.DefaultIfEmpty()
where r.Status == null || r.Status != "1"
select new
{
e
})
you want to perform the .DefaultIfEmpty() method on the quantity that you want to perform a left join onto. maybe this code snippet helps you
from e in Entities
join ml in Migration_lpgs on new { id=e.Id.ToString(), ObjectType="SASParty" } equals new { id=ml.Id.ToString(), mlog.ObjectType } into j
from e in j.DefaultIfEmpty()
where ml.Status == null || ml.Status != "1"
select e
Related
I have the following parameters:
public object GetDataByProjectCostID(string employeeid, DateTime costdate, int id = 0)
And the query:
var projectCost = (from pc in db.ProjectCosts
where pc.ProjectCostID == id
where System.Data.Entity.DbFunctions.TruncateTime(pc.CostDate) == costdate.Date
join p in db.Projects
on pc.ProjectID equals p.ProjectID
join sct in db.SubCostTypes
on pc.SubCostTypeID equals sct.SubCostTypeID
join ct in db.CostTypes
on sct.CostTypeID equals ct.CostTypeID
select new
{
p.ProjectName,
ct.CostTypeName,
ct.CostTypeID,
sct.SubCostTypeName,
pc.ProjectID,
pc.ProjectCostID,
pc.SubCostTypeID,
pc.Amount,
pc.Quantity,
pc.Note,
pc.CreatedBy,
sct.Unit,
pc.CreateDate,
pc.CostDate,
pc.ProjectCostImage
}).ToList();
Now my question is how i can add optional parameter in query.
Suppose if id not existed in request then i need to skip the where clause
where pc.ProjectCostID == id
In sql we can use when clause for that but what for in LINQ?
Thanks in advance.
try this
where (( id==0 || pc.ProjectCostID == id)
&& System.Data.Entity.DbFunctions.TruncateTime(pc.CostDate) == costdate.Date)
I use this technique in LinQ with the LinQ fluent form, you can try this:
//Make id nullable
public object GetDataByProjectCostID(string employeeid, DateTime costdate, int? id)
{
var projectCost = (from pc in db.ProjectCosts
//This expression is evaluated only if id has a value
where (!id.HasValue || pc.ProjectCostID == id)
&& System.Data.Entity.DbFunctions.TruncateTime(pc.CostDate) == costdate.Date
join p in db.Projects
on pc.ProjectID equals p.ProjectID
join sct in db.SubCostTypes
on pc.SubCostTypeID equals sct.SubCostTypeID
join ct in db.CostTypes
on sct.CostTypeID equals ct.CostTypeID
select new
{
p.ProjectName,
ct.CostTypeName,
ct.CostTypeID,
sct.SubCostTypeName,
pc.ProjectID,
pc.ProjectCostID,
pc.SubCostTypeID,
pc.Amount,
pc.Quantity,
pc.Note,
pc.CreatedBy,
sct.Unit,
pc.CreateDate,
pc.CostDate,
pc.ProjectCostImage
}).ToList();
}
I have the following SQL query which is returning one row of data exactly as expected:
select count(c.ID) as NoteCount, count(s.ClaimStatusHistoryID) as ActionCount, p.DayGoal
from Collector_Profile p
left join ClaimStatusHistory s on s.AppUserID = p.AppUserID and CONVERT(varchar(10), s.StatusDateTZ, 101) = convert(varchar(10), GETDATE(), 101)
left join Claim_Notes c on c.CollectorID = p.ID and CONVERT(varchar(10),c.PostDateTZ,101) = convert(varchar(10), GETDATE(), 101)
where p.ID = 1338
group by p.DayGoal
I am trying to convert to LINQ. When I attempt to include the DbFunctions.TruncateTime, I get an error that TruncateTime is not defined. So I have commented them out in this example, but I need to get that working as well. This is what I have so far, which compiles but throws an error:
var utcNow = DateTimeOffset.UtcNow.Date;
var query = from p in _context.Collector_Profile
join s in _context.ClaimStatusHistory on p.AppUserID
equals s.AppUserID into gs
// && DbFunctions.TruncateTime(s.StatusDateTZ) equals utcNow into gs
join c in _context.Claim_Notes on p.ID
equals c.CollectorID into gc
//&& DbFunctions.TruncateTime(c.PostDateTZ) equals utcNow into gc
from s in gs.DefaultIfEmpty()
from c in gc.DefaultIfEmpty()
where p.ID == CollectorID
group new { gs, gc } by p.DayGoal into grouped
select new UserStatistics { DayGoal = grouped.Key,
NoteCount = grouped.Count(x => x.gc.Any()),
ActionCount = grouped.Count(x => x.gs.Any()) };
return query.FirstOrDefault();
I get the following error when I run it:
InvalidOperationException: Processing of the LINQ expression 'DbSet<Collector_Profile>
.GroupJoin(
outer: DbSet<ClaimStatusHistory>,
inner: p => p.AppUserID,
outerKeySelector: s => s.AppUserID,
innerKeySelector: (p, gs) => new {
p = p,
gs = gs
})' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
Can someone help me to get this linq query working? Thanks for any assistance and examples. I've looked at lots of questions and none are doing exactly what I'm doing that I've found yet.
I believe this should work, barring issues with DateTimeOffset.Date and timezones.
Since EF Core 3 only has extremely limited support for translating GroupJoin (basically just to LEFT JOIN), you must split the query into two parts, a SQL query with LEFT JOIN and then a client side GroupBy to create the effect of GroupJoin.
var utcNowDate = DateTimeOffset.UtcNow.Date;
var dbQuery = from p in _context.Collector_Profile
where p.ID == CollectorID
join s in _context.ClaimStatusHistory.Where(s => s.StatusDateTZ.Value.Date == utcNowDate) on p.AppUserID equals s.AppUserID into gs
from s in gs.DefaultIfEmpty()
join c in _context.Claim_Notes.Where(c => c.PostDateTZ.Value.Date == utcNowDate) on p.ID equals c.CollectorID into gc
from c in gc.DefaultIfEmpty()
select new { p.DayGoal, s = s.ClaimStatusHistoryID, c = c.ID };
var query = from psc in dbQuery.AsEnumerable()
group new { psc.s, psc.c } by psc.DayGoal into grouped
select new UserStatistics {
DayGoal = grouped.Key,
NoteCount = grouped.Count(sc => sc.c != null),
ActionCount = grouped.Count(sc => sc.s != null)
};
return query.FirstOrDefault();
How can I convert this SQL Server stored procedure to a linq expression? I've got a couple of mistakes but don't know how to fix them.
Here is the stored procedure:
#MatterNumber NVARCHAR(20)
AS
DECLARE #ClientNumber INT = (SELECT TOP 1 LeadPlaintiffNumber
FROM [dbo].[vw_cmp_case_numbers]
WHERE MatterNumber = #MatterNumber)
SELECT DISTINCT
defendantid,
defendantcode,
defendantname DefendantName
FROM
(SELECT DISTINCT
fmrp.employerid DefendantId,
fmrp.employercode DefendantCode,
fmrp.employername DefendantName
FROM
vw_mpid_records fmr
LEFT JOIN
vw_mpid_records_products fmrp ON fmr.recordid = fmrp.recordid
INNER JOIN
vw_cmp_event_history fceh ON fmr.jobsitecode = fceh.jobsitecode
AND fmr.startdate < = fceh.enddate
WHERE
fceh.clientnumber = #ClientNumber
AND fmrp.employerid IS NOT NULL
AND fmrp.employercode IS NOT NULL
GROUP BY
fmrp.employerid, fmrp.employercode, fmrp.employername) yyy
ORDER BY
defendantname
Here is what I have so far for the linq but there is a error at
fmr.StartDate <= fceh.EndDate
and then I'm not sure about the group by as well
var #clientNumber = (from ccn in context.VwCmpCaseNumbers where ccn.MatterNumber == text select ccn).Take(1);
var innerQuery = from fmr in context.VwMpidRecords
join fmrp in context.VwMpidRecordsProducts on fmr.Id equals fmrp.Id
into gj
from x in gj.DefaultIfEmpty()
join fceh in context.VwCmpEventHistorys on fmr.JobsiteCode equals fceh.JobsiteCode && fmr.StartDate <= fceh.EndDate
where fceh.ClientNumber = #clientNumber &&
fmrp.EmployerID != null &&
fmrp.EmployerCode != null
group fmrp.by fmrp.EmployerID && fmrp.EmployerCode && fmrp.EmployerName
var outerQuery = (from r in innerQuery
select new
{
EmployerId = r.EmployerID,
EmployerCode = r.EmployerCode,
EmployerName = r.EmployerName
}).OrderBy(obj => obj.DefendantName);
var viewModel = outerQuery.Select(obj => new SelectOption
{
Text = obj.DefendantCode,
Value = obj.DefendantId,
});
Here are the lines that show the errors
LINQ doesn't allow you to "join" on any criteria you want: a join can only have a [this] equals [that] form. Move any criteria that doesn't match that pattern into a where clause. (This will not impact performance of the SQL query.)
Also, group by values need to be contained in a single (anonymous) object, not &&ed together.
var innerQuery = from fmr in context.VwMpidRecords
join fmrp in context.VwMpidRecordsProducts
on fmr.Id equals fmrp.Id
join fceh in context.VwCmpEventHistorys
on fmr.JobsiteCode equals fceh.JobsiteCode
where fmr.StartDate <= fceh.EndDate
where fceh.ClientNumber = #clientNumber
where fmrp.EmployerID != null && fmrp.EmployerCode != null
group fmrp by new {fmrp.EmployerID, fmrp.EmployerCode, fmrp.EmployerName};
I have three Table One is "Allowance " ,"Balance" and "TimeoffRequests" in these three table common columns are EmployeeId and TimeoffTypeId, Now i need to get the requested hours of one leave type by grouping thier timeoffTypeId and EmployeeId from the table "TimeoffRequests" , and got the "TimeOffHours". for the i wrote the code like
var query = (from tr in TimeOffRequests
where tr.EmployeeID == 9
group tr by new { tr.EmployeeID, tr.TimeOffTypeID } into res
select new
{
EmployeeID = res.Key.EmployeeID,
TimeOffTypeID = res.Key.TimeOffTypeID,
TotalHours = res.Sum(x => x.TimeOffHours)
}).AsEnumerable();
Now I need to join these results with the first table and have to get the all the employees, and timeoffTypes from the UserAllowance and corresponding TimeoffHours from the grouped table. for getting left joined query i wrote like below.
var requestResult = (from UA in UserAllowances
join UB in UserBalances on UA.EmployeeID equals UB.EmployeeID
where UA.TimeOffTypeID == UB.TimeOffTypeID && UA.EmployeeID == 9
&& UA.TimeOffType.IsDeductableType == true // LeftJoin
join rest in query on UA.EmployeeID equals rest.EmployeeID into penidngRequst
from penReq in penidngRequst.DefaultIfEmpty()
where penReq.TimeOffTypeID == UA.TimeOffTypeID
select new EmployeeTimeOffBalanceModel
{
TimeOffTypeID = UA.TimeOffTypeID != null ? UA.TimeOffTypeID : 0,
YearlyAllowanceHrs = (UA.YearlyAllowanceHrs != null) ? UA.YearlyAllowanceHrs : 0,
BalanceHours = UB.BalanceHrs != null ? UB.BalanceHrs : 0,
PendingHours = (decimal)((penReq != null) ? (penReq.TotalHours) : 0),
EmployeeID = UA != null ? UA.EmployeeID : 0,
}).ToList().Distinct();
It is giving only timeOFfType containing in grouped data,even though I wrote leftjoin for the query using the "into" and DefaultIfEmpty() keywords. the results becomes as like:
and by using the "linqPad" editor i found that it is applying the Cross or Outer Join instead of "left join" what will be the reason.
If I remove this line of code " where penReq.TimeOffTypeID
== UA.TimeOffTypeID" this showing all the timeoffTypes with cross join with repeatation like
How can I achieve left join with tables with Grouped data and showing null values if timeofftypes didn't having the any request?
You might want to move the where clause into the the on equals clause as shown below
join rest in query on new { UA.EmployeeID, UA.TimeOffTypeID } equals new { rest.EmployeeID, rest.TimeOffTypeID } into penidngRequst
from penReq in penidngRequst.DefaultIfEmpty()
You can change
where penReq.TimeOffTypeID == UA.TimeOffTypeID
to
where penReq == null || penReq.TimeOffTypeID == UA.TimeOffTypeID
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
}