There seem to be lots of questions about SQL to LINQ, but I can't seem to find examples with joined tables and grouping; specifically with a need to get data from multiple tables.
Take this simple SQL:
SELECT
s.showId, s.showName, v.venueName, Min(dateTime) startDate
FROM
shows s
INNER JOIN venues v ON s.venueId = v.venueId
INNER JOIN showDates d ON s.showId = d.showId
GROUP BY
s.showId
The best I can come up with is the following
var ungrouped = (
from s in db.Shows
join v in db.Venues on s.VenueId equals v.VenueId
join d in db.ShowDates on s.ShowId equals d.ShowId
select new { s, v, d }
).ToList();
var grouped = (
from s in ungrouped
group s by s.s.ShowId into grp
select new
{
showId = grp.Key,
name = (from g in grp select g.s.showName).FirstOrDefault(),
venue = (from g in grp select g.v.VenueName).FirstOrDefault(),
startDate = grp.Max(g => g.d.DateTime)
}
);
This works but it feels messy. I don't like:
It being split into two statements
Having to repeatedly write (from g in grp select ...).FirstOrDefault()
Bits like s.s.ShowId
How its vastly more lines of code than the SQL
This example is a simple one, it only gets worse when I have 5+ tables to join and 10+ columns to select.
Question: Is this the best way to do this, and I should just accept it; or is there a better way to write this query?
I am not sure if you are looking for something like this but it's a bit cleaner, it's not split in 2 statements and you might find it helpful. I couldn't use a dbcontext so I used lists to make sure the syntax is correct.
var res = Shows.Join(Venues,
show => show.VenueID,
venue => venue.VenueID,
(show, venue) => new { show, venue })
.Join(ShowDates,
val => val.show.ShowID,
showdate => showdate.ShowID,
(val, showDate) => new { val.show, val.venue, showDates = showDate })
.GroupBy(u => u.show.ShowID)
.Select(grp => new
{
showId = grp.Key,
name = grp.FirstOrDefault()?.show.showName,
venue = grp.FirstOrDefault()?.venue.VenueName,
startDate = grp.Max(g => g.showDates.DateTime)
});
we need to now realation beetwen them one to one or one to many , but not too far from this answer.
var GrouppedResult = Shows.Include(x=>x.Veneu).Include(x=>x.ShowDates)
.Where(x=>x.Veneu.Any()&&x.ShowDates.Any())
.GroupBy(x=>x.ShowId)
.Select(x=>///anything you want);
or
from show in Shows
join veneu in Veneu on veneu.VeneuId equals show.VeneuId
join showDates in ShowDates on showDates.ShowId=show.ShowID
group show by show.Id into grouppedShows
select new { ///what you want };
Related
I'm trying to do select with group by and sum while selecting other columns using LINQ and i come out with this
var inputList = from c in db.InputItem
join o in db.ItemsDefinition on c.ItemsDefinitionID equals o.ItemsDefinitionID
group c by new { c.ItemsDefinitionID, o.ItemsAName } into g
select new
{
Name = g.Key,
Sum = g.Sum(c => c.Quantity)
};
what I'm trying to do is to preform this SQL statement
Select i.ItemsDefinitionID,
ID.ItemsAName,
sum(Quantity) as avialable
from InputItem i
Left Outer Join ItemsDefinition ID On i.ItemsDefinitionID=ID.ItemsDefinitionID
group by i.ItemsDefinitionID,ID.ItemsAName
Warm Thanks
you can do this way too:
var inputList = d.InputItem
.GroupBy(s =>s.ItemsDefinitionID, s.ItemsDefinition.AName)
.Select(g => new
{
ItemsDefinitionID=g.Key.ItemsDefinitionID,
Name = g.Key.AName,
Available= g.Sum(s =>s.Quantity),
})
.ToList();
You don't really need to do manual joins in EF if your relationships are properly defined in the model.
This query will suffice
var result = db.ItemsDefinition.Select(id => new { id.ItemsDefinitionID,
id.ItemsAName, Quantity = id.Items.Sum(i => i.Quantity) });
Either leave the SQL generation to EF or stop using EF. There's no point in using an ORM if you keep worrying about the queries it will generate.
I'm trying to do something very simple.
I have two tables in my database that I would like to query using linq.
Table of Books, and table of GenreTypes. The result of this query would go to my web Api.
Here is a code snippet:
public List<BooksChart> GetBooksChart()
{
var results = from b in _dbcontext.Books
join g in _dbcontext.GenreTypes
on b.GenreTypeId equals g.Id
group g by g.Name into n
select (z => new BooksChart
{
category_name = n.Key,
value = n.Count()
}).ToList();
return results;
}
public class BooksChart
{
public string category_name;
public int value;
}
The results of the grouping "n" I would like to store them in BooksChart class to construct the Api.
This code is not compiling.
Previously, I was querying only one table of Books which I have divided into Books and GenreTypes.
My previous working code for querying Books was :
var results = _dbcontext
.Books
.GroupBy(x => x.GenreType)
.Select(z => new BooksPieChart
{
category_name = z.Key,
value = z.Count()
}).ToList();
return results;
EDIT
What I want to achieve in SQL is the following:
select count(*), g.Name
from books b, GenreTypes g
where b.GenreTypeId = g.Id
group by g.Name;
You are mixing the two syntax options of query and method. For query syntax you need to do the projection (select) like this:
return (from b in _dbcontext.Books
join g in _dbcontext.GenreTypes on b.GenreTypeId equals g.Id
group g by g.Name into n
select new BooksChart {
category_name = n.Key,
value = n.Count()
}).ToList();
The format of (z =>....) is the declaration of the labmda passed to the Select method.
Site notes:
As #Rabbi commented, since you are using EF, consider properly defining navigation properties. It will make querying simpler.
Side note for the sql - consider using joins instead of multiple tables in the from: INNER JOIN ON vs WHERE clause
The parentheses must surround the whole query, like so:
var results = (from b in _dbcontext.Books
join g in _dbcontext.GenreTypes
on b.GenreTypeId equals g.Id
group g by g.Name into n
select new BooksChart
{
category_name = n.Key,
value = n.Count()
}).ToList();
The compilation error is due to this (z => which is not needed at all.
I have this simple code that returns a list of products but now I need to somehow fetch the same lis of products BUT i need to add a new column or value based on a view count.
var products = db.Products.Where(p => p.ProductOwnerUserId == userID).OrderByDescending(p => p.ProductID);
this is what i have so far but i am no expert in LINQ so i was wondering if someone could help me here.
This is a kind of pseudo-code of what i am looking for
var products = from p in db.Products
join pr in db.Reviews on p.ProductID equals pr.ReviewForProductID
into g select new
{
p.*,
ProductView = g.Count(a => a.ReviewForProductID)
};
i have found my OWN answer since nothing came up from you guys... but thanx for the initial tips... im quite new with linq and complexe queries can be hard to understand and fit inside existing code/view
here is my solution:
Thank you for your first answer and well just too bad for the second one that NEVER came... FYI, since my product class is a partial class already a just added another new ProductView.cs partial class containg the new Property and my query (functionnal and tested) looks like this now:
var products = (from p in db.Products
join pr in db.Reviews on p.ProductID equals pr.ReviewForProductID
into g
select new GenericEcomDataAccess.Product
{
ProductID = p.ProductID,
ProductOwnerUserId = p.ProductOwnerUserId,
ProductCurrency = p.ProductCurrency,
ProductDescription = p.ProductDescription,
ProductPrice = p.ProductPrice,
ProductImage = p.ProductImage,
ProductName = p.ProductName,
ProductCount = g.Count()
}).Where(p => p.ProductOwnerUserId == userID)
.OrderByDescending(p => p.ProductID).AsEnumerable();
var products = db.Products.Where(p => p.ProductOwnerUserId == userID)
.OrderByDescending(p => p.ProductID)
.Select(p=> new {Product = p, Count = p.Reviews.Count()});
If you have the foreign keys set up properly
Using either a Join or GroupJoin, is there any way to produce aggregates values for fields in both the parent and child tables. Given an Orders table and an OrderDetails table, Using the 2 steps below I can obtain an aggregate (MAX) from the Orders and an aggregate (SUM) from the OrderDetails.
STEP 1:
var query = from o in orders
join d in details on o.OrderId equals d.OrderId
select new
{
order = o.OrderId,
maximum = o.UserId,
quantity = d.Quantity
};
Step 2:
var result = (from q in query
group q by q.order into g
select new
{
OrderId = g.Key,
MaxUnits = g.Max(q => q.maximum),
Available = (g.Max(q => q.maximum) - g.Sum(q => q.quantity))
});
However, when I try to combine these as in:
var finalresult = orders
.GroupJoin( details,
o => o.OrderId,
d => d.OrderDetailId,
(o, grp) => new {
OrderId = o.OrderId,
MaxUnits = grp.Max(o => o.maximum),
Available = (grp.Max(o => o.maximum) - grp.Sum(d => d.Quantity))
});
.. the value 'o' is out of scope inside the grouped set 'grp'. So grp.Max(o => o.maximum) results in an error. It appears that only aggregate values for the child table (OrderDetail) are available.
So does anyone know if it is possible to obtain aggregates from both the Child and Parent tables in a single query?
result is a single query. The beauty of LINQ and deferred execution is that no actual computation has happened in Step 1, only a query has been defined. Step 2 then builds ontop of that query to create another single query. When you execute result that query will be executed as a single block.
I recommend splitting up larger queries into smaller easier to understand pieces like in the first two examples. Using good names for the queries can make them much easier to read. For example, I might name query orderQuantities. from q in query does not convey much meaning, but from oq in orderQuantities lets me know what kind of data the query is over.
If you really think you need them together:
var query = orders.Join(details, o => o.OrderId, d => d.OrderId,
(o, d) => new {
order = o.OrderId,
maximum = o.UserId,
quantity = d.Quantity
}).GroupBy(oq => oq.order)
.Select(g => new {
OrderId = g.Key,
MaxUnits = g.Max(q => q.maximum),
Available = (g.Max(q => q.maximum) - g.Sum(q => q.quantity))
});
Now that is ugly...
Here's what I have so far:
var bestReason =
from p in successfulReasons
group p by p.Reason into g
select new { Excuse = g.Key, ExcuseCount = g.Count() };
What I need to do now is return one reason that is the best reason, determined by which were successful in the past.
Sample data:
ID,Reason
---------
0,Weather
1,Traffic
2,Illness
3,Weather
4,Traffic
5,Traffic
6,Pirates
should return "Traffic"
Would like to do it all in one LINQ statement, if possible.
Thanks.
EDIT: If there are 7 Pirate Attacks, and 7 Traffic Accidents, I'm ok with returning either one (the first alphabetically would be fine).
var bestReason = successfulReasons
.GroupBy(r => r.Reason)
.OrderByDescending(grp => grp.Count())
.First().Key;
If I understand your question correctly, you can do:
string bestReason =
(from p in successfulReasons
orderby p.Reason
group p by p.Reason into g
orderby g.Count() descending
select g.Key).FirstOrDefault();
var group = excuses.GroupBy(m => m.Reason)
.OrderByDescending(m => m.Count())
.Select(m => m.Key)
.FirstOrDefault();
Which produces the following sql statement:
SELECT TOP (1) [t1].[Reason]
FROM (
SELECT COUNT(*) AS [value], [t0].[Reason]
From [dbo].[Excuses] As [t0]
GROUP BY [t0].[Reason]
) As [t1]
ORDER BY [t1].[value] DESC
Since this is a moderately complicated IQueryable expression, you might consider compiling it to speed up the response time:
Func<ExcusesDataContext, string> commonResult = CompiledQuery.Compile(
(ExcusesDataContext c) => c.Excuses.GroupBy(m => m.Reason).OrderByDescending(m => m.Count()).Select(m => m.Key).FirstOrDefault()
);
Console.WriteLine(commonResult(new ExcusesDataContext()));
Console.ReadLine();
You could also just call the stored procedure via a repository and snag the particular value that you're looking for. This would be the fastest path to happiness, but the least fun to maintain:
string excuse = this.repo.Excuses.MostCommonFor(ProblemList.BeingLate);