I have a LINQ to construct a list of buildings, which has a field consisting of a list of Users:
List<Building> buildings = (from b in db.Buildings
join u in db.BuildingUsers on b.BuildingId equals u.BuildingId into bUsers
orderby b.BuildDate descending
select Building.Create(b, bUsers)).ToList();
The UserName is not always the user I want. What I really want is the first User that was ever entered, which I think is correct to assume it would be the BuildingUser with the lowest UserID. So I do the Order By:
List<Building> buildings = (from b in db.Buildings
join u in db.BuildingUsers on b.BuildingId equals u.BuildingId into bUsers
orderby u.UserId ascending
select Building.Create(b, bUsers)).ToList();
That works great, except now my overall list of buildings is in an awkward order. The bUsers is passed into the Create method in the correct order, and I handle the FirstOrDefault() to get the first user and handle the rest. I want the end-result of the overall building list to be in order by BuildDate.
List<Building> buildings = (from b in db.Buildings
join u in db.BuildingUsers on b.BuildingId equals u.BuildingId into bUsers
orderby u.UserId ascending, b.BuildDate descending
select Building.Create(b, bUsers)).ToList();
Of course this doesn't work, because now it first sorts by UserId, then by BuildDate and doesn't quite turn out correctly.
So I want one orderby for the Join, and a different orderby for the main result. I'd rather not split them into multiple methods so I don't have to alter the way I construct my building objects.
You were right in using orderby b.BuildDate descending because that is the ordering expected in your output collection buildings. The ordering of the subcollection bUsers should be performed when passing it to Building.Create():
List<Building> buildings = (from b in db.Buildings
join u in db.BuildingUsers on b.BuildingId equals u.BuildingId into bUsers
orderby b.BuildDate descending
select Building.Create(b, bUsers.OrderBy(bu => bu.UserId))).ToList();
Based on what you are saying above I might suggest changing Building.Create to accept a single user, in which case you could perform the FirstOrDefault() in this query:
List<Building> buildings = (from b in db.Buildings
join u in db.BuildingUsers on b.BuildingId equals u.BuildingId into bUsers
orderby b.BuildDate descending
select Building.Create(b, bUsers.OrderBy(bu => bu.UserId).FirstOrDefault())).ToList();
Related
I'm new to using LINQ to join collections and peforming calculations. I have the following query which joins a collection of RailwayStation objects and ExpenditureData objects joined by StationId.
var joinedData = (from s in stations join e in expenditureData on s.stationId
equals e.StationId select s).Distinct();
What LINQ do I need to get the top 10 stations which the highest expenditureAmount (which is a property in expenditureData) from the joinedData collection I have created?
I'm trying to group the data by StationCargoCode (a property in
Station object) to get the top 10 StationCargoCodes with the highest
expenditure data and get the total expenditure data too.
You could try the following query:
var joinedData = (from s in stations
join e in expenditureData
on s.stationId equals e.StationId
group by s.StationCargoCode into gr
select new
{
StationCargoCode = gr.Key,
TotalExpenditureAmount = gr.Sum(ed=>ed.expenditureAmount)
}).OrderByDescending(sc=>sc.TotalExpenditureAmountExpenditureAmount)
.Take(10);
Initially we join our data based on the station Id.
Then we group by the joined results based on the StationCargoCode.
After the grouping, we project each group to an anonymous object with two values, one the key used for the grouping and the other the sum of the expenditureAmount for that key.
Last, we order by our results in descending order based on the TotalExpenditureAmount and we pick up the first 10.
You can try this:
var joinedData = (from s in stations
join e in expenditureData on s.stationId equals e.StationId into g
select new{s,g})
.OrderByDescending(v=>v.g.Max(r=>r.expenditureAmount))
.Take(10)
.Select(v=>v.s);
Update
var joinedData = (from s in stations
join e in expenditureData on s.stationId equals e.StationId into g
select new{s.StationCargoCodes ,Max=g.Max(r=>r.expenditureAmount)})
.OrderByDescending(v=>v.Max)
.Take(10);
If you want just the StationCargoCodes then add a Select call at the end like this:
.Select(e=>e.StationCargoCodes);
I'm sorry for telling that I've a little bit weak on LINQ, I always do write SQL query before I start working on the complicated LINQ.
I want to ask that how to convert this SQL Query into LINQ with LEFT JOIN with multiple ON conditons with the OR operator.,
m.MerchandiseId will be use for twice in ON condition
SELECT
*
FROM
Inbox AS i
INNER JOIN [User] AS u ON i.FromUserId = u.UserId
LEFT OUTER JOIN Merchandise AS m ON
u.MerchandiseId = m.MerchandiseId
OR
i.ToMerchantId = m.MerchandiseId
WHERE
i.ToCompanyId = 10
OR
i.FromCompanyId = 10
var message = (from i in db.Inbox
join u in db.User on i.FromUserId equals u.UserId
join m in db.Merchandise on u.MerchandiseId equals m.MerchandiseId //here I want to ON i.MerchantId = m.MerchandiseId, but it doesn't allow
where i.ToCompanyId == user.CompanyId || i.FromCompanyId == user.CompanyId
orderby i.CreatedAt descending
group m.MerchandiseId by new { m.MerchandiseId, m.MerchandiseName } into grp
select new
{
MerchandiseId = grp.Key.MerchandiseId,
MerchandiseName = grp.Key.MerchandiseName,
InboxMessage = (from e in db.Inbox
join us in db.User on e.FromUserId equals us.UserId
join mer in db.Merchandise on us.MerchandiseId equals mer.MerchandiseId
where mer.MerchandiseId == grp.Key.MerchandiseId
orderby e.CreatedAt descending
select e.InboxMessage).FirstOrDefault(),
CreatedAt = (from e in db.Inbox
join us in db.User on e.FromUserId equals us.UserId
join mer in db.Merchandise on us.MerchandiseId equals mer.MerchandiseId
where mer.MerchandiseId == grp.Key.MerchandiseId
orderby e.CreatedAt descending
select e.CreatedAt).FirstOrDefault(),
}).ToList();
The bottom LINQ Query I've write for it. However, I just can work on the left join with multiple ON clause in LINQ. Appreciate if someone would help me on this. Thanks!
I don't believe Linq supports the use of the OR operator with multiple columns, but that said, I wouldn't use OR even in SQL as it makes the join's intention unclear and it also obscures where the data originated from - it also isn't immediately clear what happens if there are multiple matches for each column. Instead I would JOIN twice on the different columns and let the projection-clause handle it:
SELECT
*
FROM
Inbox
INNER JOIN [User] AS u ON i.FromUserId = u.UserId
LEFT OUTER JOIN Merchandise AS userMerchant ON u.MerchandiseId = userMerchant.MerchandiseId
LEFT OUTER JOIN Merchandise AS inboxMerchant ON Inbox.ToMerchantId = inboxMerchant .MerchandizeId
WHERE
Inbox.ToCompanyId = 10
OR
Inbox.FromCompanyId = 10
This can then be translated into Linq using the LEFT OUTER JOIN approach ( How to implement left join in JOIN Extension method )
Note that if you're using Entity Framework then you don't need to worry about doing any of this at all! Just use Include:
var query = db.Inbox
.Include( i => i.User )
.Include( i => i.User.Merchandise )
.Include i => i.Merchandise )
.Where( i => i.ToCompanyId = 10 || i.FromCompanyId == 10 );
I have following values in a table and I need a LINQ query which satisfies the conditions below.
Select a single record, which has the maximum RevOrder.
If there are multiple records with same maximum RevOrder, then select the record with maximum RevDate
If RevDate's are equal too then get the record with maximum RevisionStatusID.
RevisionStatusID RevDate RevOrder
1 12/01/2012 0
2 14/02/2013 1
3 10/02/2013 2
4 11/01/2013 2
5 11/01/2013 3
I tried the below query but it gives an error.
var DocRevIDs = (from tbh in context.tblDocumentHeaders
join tbr in context.tblDocumentRevisions
on tbh.DocumentHeaderID equals tbr.DocumentHeaderID
where tbh.DocumentHeaderID == tb.DocumentHeaderID
select tbr).Max(o => new { o.RevOrder,o.RevisionDate,o.DocumentRevisionID });
Unable to process the type
'<>f__AnonymousType523[System.Nullable1[System.Double],System.Nullable`1[System.DateTime],System.Int32]',
because it has no known mapping to the value layer.
Instead of trying to use Max(), use a combination of orderby clauses and then use FirstOrDefault(). This would be the approach you would take to do it via straight SQL.
So that would give you something similar to:
var docRevision = (from tbh in context.tblDocumentHeaders
join tbr in context.tblDocumentRevisions
on tbh.DocumentHeaderID equals tbr.DocumentHeaderID
where tbh.DocumentHeaderID == tb.DocumentHeaderID
orderby tbr.RevOrder descending, tbr.RevisionDate descending, tbr.DocumentRevisionID descending
select tbr).FirstOrDefault();
you should Order the records in the linq:
var DocRevIDs = (from tbh in context.tblDocumentHeaders
join tbr in context.tblDocumentRevisions
on tbh.DocumentHeaderID equals tbr.DocumentHeaderID
where tbh.DocumentHeaderID == tbr.DocumentHeaderID
orderby tbr.RevOrder descending, tbr.RevDate descending, tbr.RevisionStatusID descending
select tbr).First();
If I have a query like:
var results = from j1 in context.users
join j2 in context.points on j1.UserId equals j2.UserId
join j3 in context.addresses on j1.UserId equals j3.UserId
select new { j1.Username, j2.points, j3.address1 }
Now if I want to group by:
var results = from j1 in context.users
join j2 in context.points on j1.UserId equals j2.UserId
join j3 in context.addresses on j1.UserId equals j3.UserId
group j1 by j1.CountryCode into g1
select new { j1.Username, j2.points, j3.address1 }
So when I group by, it seems I have to use the 'into' keyword which I guess does some sort of projection? Now I'm not sure how to reference j1.Username, j2.points now?? I see there is a 'Key' property but that doesn't let me navigate to the columns.
How to I get access to the joined columsn after grouping?
So when I group by, it seems I have to use the 'into' keyword which I
guess does some sort of projection?
There are two pure projection operators - Select and SelectMany.
Now I'm not sure how to reference j1.Username, j2.points now?
You have groups, which contain j1 entities only. And each group is a collection of j1 entities. You can aggregate results. But there is no single j1 entity, which you can get Username of. Thus you have groups of j1, you can't access j2 or j3. Your joins used only to filter users by points and addresses. After that you continued to query only users.
I see there is a 'Key' property but that doesn't let me navigate to
the columns.
Key property is a key of each group. In your case it will be CountryCode, which is same for all users in group.
Here is query which returns user name, address, and total points. How it works? First you join user and address with simple join. But you should project results into anonymous object which contain both user and answer. Otherwise answer will be 'lost' from scope, just as in your original query. So far so good - we have both user and answer. Now we need total points. For this GroupJoin is used - its not just correlates user-address pair with points, but also groups results (i.e. points) into points group. Now you have all data accessible - user and address are in ua object, and points related to user are in points group. You can calculate totals:
from u in context.users
join a in context.addresses on u.UserId equals a.UserId
select new { u, a } into ua
join p in context.points on ua.u.UserId equals p.UserId into points
select new
{
ua.u.Username,
ua.u.CountryCode, // need for grouping by country
ua.a.address1,
Points = points.Sum()
}
// last grouping by country. You can omit it
into x
group x by x.CountryCode into g
select g;
BTW why not to use u, p and a variables for user, point and address? Why j1, j2, j3?
I have an SQL query that I built for a tool a while ago and I'm remaking the tool in MVC and using LINQ to Entities.
I can't seem to figure out how to sort my list of Brands by weighting my Cars by man hours and their testing value.
Here's the SQL query I had in the old tool:
SELECT Brand.ID, SUM(Car.EstManHours) - SUM(Car.EstManHours) * CAST(AVG(1.00 * TestingStatus.Value) AS DECIMAL(9 , 2)) / 100 AS Weighting
FROM TestingStatus INNER JOIN Car ON TestingStatus.ID = Car.StatusID
INNER JOIN Team ON Car.TeamID = Team.TeamID
RIGHT OUTER JOIN Brand
LEFT OUTER JOIN SubCategory ON Brand.ID = SubCategory.BrandID ON Car.SubCategoryID = SubCategory.ID
WHERE (Car.IsPunted == 'False')
GROUP BY Brand.YearID, Brand.FeatID
HAVING (Brand.YearID = #BrandYearID)
ORDER BY Weighting DESC
I've tried this, but whether I put descending or ascending the order doesn't actually change in the list, it keeps the sorting by Id:
var brands = (from b in _context.Brands
join s in _context.SubCategorys on f.Id equals s.BrandId
join c in _context.Cars on s.Id equals c.SubCategoryId
where (f.YearId == yearId && c.IsPunted == false)
orderby (c.ManHoursEst - (c.ManHoursEst * c.TestingStatu.Value / 100)) descending
select b).Distinct().ToList();
Would appreciate help on this conversion!
Thanks.
EDIT:
I'm now trying to get the order by and group by to work correctly.
The following query is listing tons of duplicates and not ordering properly as I don't think my weighting is done correctly.
var brands = (from b in _context.Brands
join s in _context.SubCategorys on f.Id equals s.BrandId
join c in _context.Cars on s.Id equals c.SubCategoryId
where (f.YearId == yearId && c.IsPunted == false)
let weighting = c.ManHoursEst - (c.ManHoursEst * c.TestingStatu.Value / 100)
orderby weighting descending
group b by b.Id).SelectMany(x=>x).ToList();
Any ideas?
Distinct does not preserve sorting. That is your problem.
You could do a group by like in your SQL to mimic the Distinct and perform everything server side.