Linq2Sql join by multiple columns (by OR operator)? - c#

Mow I can translate this:
SELECT *
FROM vectors as v
INNER JOIN points as p
ON v.beginId = p.id OR v.endId = p.id
Into linq2sql statement? Basically I want this:
var query = from v in dc.vectors
join p in dc.points on p.id in (v.beginId, v.endId)
...
select ...;
I know, I can do this dirty through Union construction, but is there a better way than duplicating most of the query?

You can't have an on clause in linq-to-sql with an or. You need to do:
var result = from v in dc.vectors
from p in dc.points
where p.id == v.beginId || p.id == v.endId
select new { v, p };
Equivalent to the sql of:
SELECT *
FROM vectors as v,
points as p
WHERE v.beginId = p.id
OR v.endId = p.id

Related

Why LINQ Sum() throws "System.Collections.Generic.KeyNotFoundException"?

Here I have
System.Collections.Generic.KeyNotFoundException: 'The given key 'EmptyProjectionMember' was not present in the dictionary.'
var res = (from c in _context.Check
join cp in _context.CheckProduct on c.Id equals cp.CheckId
join p in _context.Product on cp.ProductId equals p.Id
where c.Date.Date == date.Date
select (cp.Quantity * Decimal.ToDouble(p.Price))).Sum();
But when I write this, the code is working:
var res = (from c in _context.Check
join cp in _context.CheckProduct on c.Id equals cp.CheckId
join p in _context.Product on cp.ProductId equals p.Id
where c.Date.Date == date.Date
select (cp.Quantity * Decimal.ToDouble(p.Price)));
double sum = 0;
foreach(var el in res)
{
sum += el;
}
Why Sum() is not working?
Try the following:
res = (from c in _context.Check
join cp in _context.CheckProduct on c.Id equals cp.CheckId
join p in _context.Product on cp.ProductId equals p.Id
where c.Date.Date == date.Date
select (cp.Quantity * Decimal.ToDouble(p.Price)))
.DefaultIfEmpty(0)
.Sum();
Looks like there is nothing to select, so in that case, default to 0.
In the second case, you try to iterate an empty enumerable so it won't even go into the for each clause.
I was getting the same error when calling Any with an unsatisfied filter
public static bool IsBooking(this Address address)
=> address.ReferenceTypes.Any(referenceType
=> referenceType == ReferenceType.Booking
&& address.IsActive);
After first making sure that some elements exist, it is working - but it doesn't really make sense as now I'm calling Any twice
public static bool IsBooking(this Address address)
=> address.ReferenceTypes.Any()
&& address.ReferenceTypes.Any(referenceType
=> referenceType == ReferenceType.Booking
&& address.IsActive);
You need to call .ToList() before .Sum().

LINQ version of sql query with multiple left joins, group by, and datetime conversion

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();

Can we use if statement in linq for deciding to join or not?

I want to include a table in some conditions in Linq.
I am looking for sth like this:
var query = from x in context.Messages
if(x.senderID != 0)
{
join et in context.ETs on x.senderID equals et.ID
}
where x.Getter == SSN
select new { x.id, x.message}
Is this kind of approach possible or do I have to write two different linq queries and then I will combine them?
var query = from a in context.Messages
join b in context.ETs
on a.senderID equals b.ID
into temp
from b in temp.DefaultIfEmpty()
where a.Getter == SSN
select new { a.id, a.message}

Converting SQL to LINQ with INNER JOIN()?

I am struggling with how to write the below equivalent as LINQ. Truly I guess I am only struggling with how I represent the INNER JOIN () portion. Is that called a Nested Join? Anonymous Join? I am not even sure. Anyway, big thanks to anyone who can point me true. Even if it is just what this is called so I can BING it properly.
SELECT p.PersonID, p.FirstName, p.MiddleName, p.LastName, cp.EnrollmentID, cp.EnrollmentDate, cp.DisenrollmentDate
FROM vwPersonInfo AS p
INNER JOIN (
SELECT c.ClientID, c.EnrollmentID, c.EnrollmentDate, c.DisenrollmentDate
FROM tblCMOEnrollment AS c
LEFT OUTER JOIN tblWorkerHistory AS wh
ON c.EnrollmentID = wh.EnrollmentID
INNER JOIN tblStaffExtended AS se
ON wh.Worker = se.StaffID
WHERE (wh.EndDate IS NULL OR wh.EndDate >= getdate())
AND wh.Worker = --WorkerID Param Here
) AS cp
ON p.PersonID = cp.ClientID
ORDER BY p.PersonID
just put the inner query in its own variable. (It will be translated into one single SQL expression)
var innerQuery = from x in db.tblCMOEnrollment
where ...
select ...;
var query = from a in vwPersonInfo
join b innerQuery on p.PersonID equals cp.ClientID
select ...;
I think you can do this by writing a second method and joining on that method:
private static IEnumerable<Table> GetData(int joinKey)
{
return (from x in context.TableB.Where(id => id.Key == joinKey select x).AsQueryable();
}
Then you can do your normal query:
var query = from c in context.TableA
join GetData(c.PrimaryKeyValue)

How do I build up a LINQ => SQL / entities query (with joins) step-by-step?

I have the following two LINQ queries:
public int getJobsCount()
{
var numJobs =
(from j in dbConnection.jobs
join i in dbConnection.industries on j.industryId equals i.id
join c in dbConnection.cities on j.cityId equals c.id
join s in dbConnection.states on j.stateId equals s.id
join pt in dbConnection.positionTypes on j.positionTypeId equals pt.id
select j).Count();
return numJobs;
}
public List<Job> getJobs()
{
var jobs =
(
from j in dbConnection.jobs
join i in dbConnection.industries on j.industryId equals i.id
join c in dbConnection.cities on j.cityId equals c.id
join s in dbConnection.states on j.stateId equals s.id
join pt in dbConnection.positionTypes on j.positionTypeId equals pt.id
orderby j.issueDatetime descending
select new Job { x = j.field, y = c.field, etc }
).Skip(startJob - 1).Take(numJobs);
return jobs;
}
There's a lot of duplicate code in there - the "from", and "join" lines are identical, and I'll be adding in some "where" lines as well that will also be identical.
I tried adding a method that returned an IQueryable for the first part:
public IQueryable getJobsQuery()
{
var q =
from j in dbConnection.jobs
join i in dbConnection.industries on j.industryId equals i.id
join c in dbConnection.cities on j.cityId equals c.id
join s in dbConnection.states on j.stateId equals s.id
join pt in dbConnection.positionTypes on j.positionTypeId equals pt.id;
return q;
}
...but I get "a query body must end with a select clause or a group clause".
If I add a select clause on to the end off that function, I can't call count() on the result:
// getJobsQuery:
var q = from j in dbConnection.jobs
join i in dbConnection.industries on j.industryId equals i.id
join c in dbConnection.cities on j.cityId equals c.id
join s in dbConnection.states on j.stateId equals s.id
join pt in dbConnection.positionTypes on j.positionTypeId equals pt.id
select new { a = j.y, b = c.z }
// another method:
var q = getJobsQuery();
var numJobs = q.Count(); // "IQueryable doesn't contain a definition for count"
Is there a way to build up this query step-by-step to avoid duplicating a whole lot of code?
There are two ways of writing LINQ-queries, and though it doesn't really matter witch one you use it's good to know both of them cause they might learn you something about how LINQ works.
For instance, you have a set of jobs. If you were to select all jobs with an industryId of 5 (wild guess of data-types) you'd probably write something like this:
from j in dbConnection.jobs
where j.inustryId == 5
select j;
The very same query can also be written like this
dbConnections.jobs.Where(j => j.industryId == 5);
Now, I'm not here to preach saying one way is better than the other, but here you can clearly see how LINQ using the extension-methods syntax automatically selects on the iterated object (unless you do a select), whereas in the query-syntax you must do this explicitly. Also, if you were to add inn another where clause here it would look something like this:
from j in dbConnection.jobs
where j.inustryId == 5 // not using && here just to prove a point
where j.cityId == 3 // I THINK this is valid syntax, I don't really use the query-syntax in linq
select j;
While in the extension-methods you can just append more method-calls like so:
dbConnections.jobs.Where(j => j.industryId == 5)
.Where(j => j.cityId == 3);
Now this is good to know cause this means you can just put your linq-query inside a function an continue querying it. And all you need to do to make it work in your case is just explicitly select the starting variable j, or all the variables you need like so:
var q =
from j in dbConnection.jobs
join i in dbConnection.industries on j.industryId equals i.id
join c in dbConnection.cities on j.cityId equals c.id
join s in dbConnection.states on j.stateId equals s.id
join pt in dbConnection.positionTypes on j.positionTypeId equals pt.id;
select new {j = j, i = i, c = c, s = s, pt = pt };
return q;
Then you should be able to do for instance this:
getJobsQuery().Where(a => a.i.id == 5); // I used a as a name for "all", like the collection of variables
or using the query-syntax
from a in getJobsQuery()
where a.i.id == 5
select a;
Would this be better solved by returning a set of data (e.g. the common data) and querying for a subset of that data?
E.g. [pseudocode]
var allJobs =
(from j in dbConnection.jobs
join i in dbConnection.industries on j.industryId equals i.id
join c in dbConnection.cities on j.cityId equals c.id
join s in dbConnection.states on j.stateId equals s.id
join pt in dbConnection.positionTypes on j.positionTypeId equals pt.id
select j);
var myJobs = allJobs.OrderBy(j => j.issuedate).skip(expr).Take(allJobs.Count);
or similar...

Categories

Resources