Need help on LINQ query left joins and multiple fields - c#

I have the following SQL query that I would like to rewrite into LINQ:
SELECT gp.Name
, r.DateOfRace
, ISNULL(SUM(rr.Points), 0) AS Points
FROM Races r
INNER JOIN GrandPrix gp
ON r.GrandPrixId = gp.GrandPrixId
LEFT JOIN Predictions p
ON p.RaceId = r.RaceId
AND p.AdditionalUserInformationId = 2
LEFT JOIN RaceResults rr
ON p.DriverId = rr.DriverId
AND p.FinishPosition = rr.FinishPosition
AND p.RaceId = rr.RaceId
WHERE r.Season = 2010
GROUP BY gp.Name
, p.RaceId
, r.DateOfRace
And this is much I got, when it's still working:
from races in Races
join grandprix in GrandPrixes
on races.GrandPrixId equals grandprix.GrandPrixId
from Predictions in Predictions.Where(v => v.RaceId == races.RaceId).DefaultIfEmpty()
select new
{
DateOfRace = races.DateOfRace,
GrandPrix = grandprix.Name,
}
When I go further, things go wrong - I can't for example get the AND p.AdditionalUserInformationId = 2 right.
I hope somebody can help!

You can do the following:
join p in Predictions
on new { p.RaceId, p.AdditionalUserInformationId } =
new { r.RaceId, AdditionalUserInformationId = 2 } into ps
from p in ps.DefaultIfEmpty()
join rr in RaceResults
on new { p.DriverId, p.RaceId, p.FinishPosition } =
new { rr.DriverId, rr.RaceId, rr.FinishPosition } into rrs
from rr in rrs.DefaultIfEmpty()
You use the ability of C# to structurally compare anonymous types. Two anonymous types are created with the same properties, which makes them instances of the same class. These instances can then be compared.

join grandprix in GrandPrixes
on new {races.GrandPrixId, p.AdditionalUserInformationId} equals new {grandprix.GrandPrixId,2}

Related

LINQ to Entities grouping

I have written the following SQL-query that illustrates what I'm trying to achieve:
SELECT
K.id,
SUM(BP.pris)
FROM Kunde K
JOIN Booking BO ON K.id = BO.Kunde_id
JOIN Bane_Booking BB ON BO.id = BB.Booking_id
JOIN Banepris BP ON BO.starttid = BP.time
WHERE BO.gratis = 0
GROUP BY K.id;
Basically it's retrieving the total amount spend by customers. Now I'm trying to convert this query into LINQ, as I'm using LINQ to Entities in my application. This is what I have so far:
from kunde in context.Kunde
join booking in context.Booking on kunde equals booking.Kunde
join banepris in context.Banepris on booking.starttid equals banepris.time
from baneBooking in booking.Bane
select new { Kunde = kunde, Booking = booking, BaneBooking = baneBooking, Banepris = banepris };
I would like however to be able to group these in the same LINQ-query so I dont have to group them manually afterwards. How would i go about achieving this?
I need to get the "kunde"-object and the sum of "banepris.pris".
Without your database, I can't test this, but this should be close:
from K in context.Kunde
join BO in context.Booking on K.id equals BO.Kunde_id
join BP in context.Banepris on BO.starttid equals BP.time
where BO.gratis == 0
group new { Kunde = K, Pris = BP.pris } by K.id into g
select new { ID=g.Key, Kunde = g.First().Kunde, Sum = g.Sum(k=>k.Pris)}

Adding Count Column to LINQ query Based on Joined Table

So I have a SQL view that I've created that provides me what I need. Essentially it's a job position billeting system that shows how many positions have been authorized vs filled (or assigned).
SELECT Companies.Name AS Company, Grades.Name AS Grade, Series.Name
AS Series, Positions.Authorized, COUNT(People.PersonId) AS Assigned
FROM Companies INNER JOIN
Positions ON Companies.Id = Positions.CompanyId INNER JOIN
Series ON Positions.SeriesId = Series.Id INNER JOIN
Grades ON Positions.GradeId = Grades.Id INNER JOIN
People ON Positions.CompanyId = People.CompanyId AND
Positions.SeriesId = People.SeriesId AND Positions.GradeId = People.GradeId
GROUP BY Companies.Name, Grades.Name, Series.Name, Positions.Authorized
Now what I'd like to be able to do is recreate this in a LINQ query. I've almost got it where I need it; however, I can't figure out how to add the counted column at the end that's based on the People table.
Here's my current LINQ query:
var query = from a in db.Companies
join b in db.Positions on a.Id equals b.CompanyId
join c in db.Series on b.SeriesId equals c.Id
join d in db.Grades on b.GradeId equals d.Id
join e in db.People on new { b.CompanyId, b.SeriesId, b.GradeId } equals new { e.CompanyId, e.SeriesId, e.GradeId }
group a by new { CompanyName = a.Name, GradeName = d.Name, SeriesName = c.Name, b.Authorized, e.PersonId } into f
select new { Company = f.Key.CompanyName, Grade = f.Key.GradeName, Series = f.Key.SeriesName, f.Key.Authorized, Assigned = /* needs to be Count(People.PersonId) based on last join */ )};
Thanks in advance for any help you can provide!
Figured it out. The reason why it was posting multiple rows and not doing a proper count on the same row was because in my "group by" I added in "e.PersonId" when it should have simply been removed. I also had to add a few things to make it work on the front-end razor views since it's an anonymous type (this doesn't have anything to do with the original question, but thought I'd give reason to the changes). So the person who removed their answer, you were partially right, but the reason it wasn't working was because of the additional fieldin the group by:
dynamic query = (from a in db.Companies
join b in db.Positions on a.Id equals b.CompanyId
join c in db.Series on b.SeriesId equals c.Id
join d in db.Grades on b.GradeId equals d.Id
join e in db.People on new { b.CompanyId, b.SeriesId, b.GradeId } equals new { e.CompanyId, e.SeriesId, e.GradeId }
group a by new { CompanyName = a.Name, GradeName = d.Name, SeriesName = c.Name, b.Authorized } into f
select new { Company = f.Key.CompanyName, Grade = f.Key.GradeName, Series = f.Key.SeriesName, Authorized = f.Key.Authorized, Assigned = f.Count()}).AsEnumerable().Select(r => r.ToExpando());
And what it looks like on the page:

Left outer join off of group by dataset using linq

I am attempting to write the following SQL as a linq query.
SELECT grp.OrganisationId,
grp.OrderCount,
organisations.Name
FROM (select OrganisationId,
count(*) as OrderCount
from orders
where 1 = 1
group by OrganisationId) grp
LEFT OUTER JOIN organisations on grp.OrganisationId = organisations.OrganisationId
WHERE 1 = 1
The where clauses are simplified for the benefit of the example.
I need to do this without the use of navigational properties.
This is my attempt:
var organisationQuery = ClientDBContext.Organisations.Where(x => true);
var orderGrouped = from order in ClientDBContext.Orders.Where(x => true)
group order by order.OrganisationId into grouping
select new { Id = grouping.Key.Value, OrderCount = grouping.Count() };
var orders = from og in orderGrouped
join org in organisationQuery on og.Id equals org.Id
select(x => new OrganisationOrdersReportPoco()
{
OrganisationNameThenCode = org.Name,
TotalOrders = og.OrderCount
});
But I am getting an error of...
Type inference failed in the call to 'Join'
From previous threads, I believe this is because I have "lost the join with order" (but I don't understand why that matters when I am creating a new recordset of Organisation, Count).
Thanks!
I understand you may believe navigation properties are the solution here, but if possible, please can we keep the discussion to the join off of the group by as this is the question I am trying to resolve.
You are mixing lambda and LINQ expressions. Change select to:
select new OrganisationOrdersReportPoco()
{
OrganisationNameThenCode = org.Name,
TotalOrders = og.OrderCount
};
If i understood your model correctly you could try this instead:
var orders = ClientDBContext.Organisations.Select(org => new OrganisationOrdersReportPoco
{
OrganisationNameThenCode = org.Name,
TotalOrders = org.Orders.Count()
}).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
}

Concatenating two ViewModels

I have two instances of the same ViewModel that I would like to concatenate:
var queryNew = from a in ICDUnitOfWork.AlphaGroups.Find()
join e in ICDUnitOfWork.Alphas.Find()
on a.AlphaGroupID equals e.AlphaGroupID into g
join c in ICDUnitOfWork.Codes.Find()
on a.CodeID equals c.CodeID into co
select new HomeSearchViewModel
{
Alphas = g,
AlphaGroups = a,
AlphaGroupCode = co.FirstOrDefault(),
SearchTerm = searchTerm,
AlphasCodes = null
};
var codequery = from a in ICDUnitOfWork.Alphas.Find()
join c in ICDUnitOfWork.Codes.Find()
on a.CodeID equals c.CodeID into g
select new HomeSearchViewModel
{
AlphasCodes = g
};
var allResults = queryNew.Concat(codequery);
This gives me an error stating:
The type 'ICD.ViewModels.HomeSearchViewModel' appears in two
structurally incompatible initializations within a single LINQ to
Entities query. A type can be initialized in two places in the same
query, but only if the same properties are set in both places and
those properties are set in the same order.
How can I join these results together?
Well the solution was really dumb on my part. I added a navigation property to the table I was trying join and everything is working now.
whoops!
Concat isn't really the right thing to do here, a simple for loop should be enough. From the looks of your query you could possibly use the AlphaGroupCode as your unique identifier for the mapping e.g.
var codequery = ...
select new HomeSearchViewModel
{
AlphaGroupCode = c.FirstOrDefault()
AlphasCodes = g
};
foreach (var q in queryNew)
{
q.AlphaCodes = codequery.Where(x => x.AlphaGroupCode == q.AlphaGroupCode)
.FirstOrDefault()
.AlphaCodes;
}
You could try evaluating the queries before hand, calling something like "ToList()":
var allResults = queryNew.ToList().Concat(codequery.ToList());
If you don't mind doing it as two queries then calling AsEnumerable() will concatenate in local memory with no problems.
var result = queryNew.AsEnumerable().Concat(codequery);
Here the AsEnumerable() will still defer execution of the queries (which is what your code seems to suggest), however if you want immediate execution do as Arthur suggests and call a cacheing function e.g. ToList() or ToArray()
You must fill with null all other properties
var codequery = from a in ICDUnitOfWork.Alphas.Find()
join c in ICDUnitOfWork.Codes.Find()
on a.CodeID equals c.CodeID into g
select new HomeSearchViewModel
{
Alphas = null,
AlphaGroups = null,
AlphaGroupCode = null,
SearchTerm = null,
AlphasCodes = g
};
var allResults = queryNew.Concat(codequery);

Categories

Resources