Linq, how to write regular query with aggregate function? - linq

on my page, I need to show a list of tools.
var tools = _toolRepository.GetAll().Where(t => t.IsActive == true).OrderByDescending(t => t.PostDate).Take(50).ToList();
I also need to show how many Votes each tool got, I can write a separate query to do that, but that will double the number of queries.
is there a way to combine them together?
here's my database structure.
Tool
id
name
postDate
ToolVote
id
ToolId
UserId

How about:
var tools = _toolRepository.GetAll().Where(t => t.IsActive == true).OrderByDescending(t => t.PostDate).Take(50).Select(tool => new { Tool = tool, votes = ToolVotes.Where(tv => tv.ToolID == tool.ToolID)} ).ToList();

Assuming that you have 1 to * relationship you can use left join.
I can't test this query but it should look something like this
var query = (from tool in _toolRepository.GetAll().Where(t => t.IsActive == true).OrderByDescending(t => t.PostDate).Take(50)
join toolVote in toolVotes on tool.id equals toolVote.ToolId
into toolsTemp
from toolTemp in toolsTemp.DefaultIfEmpty()
select new { Tool = tool, Count = toolTemp == null ? 0 : toolTemp.Count})
.ToList();

Related

EF Linq query with conditional include

So I have the following Linq query:
var member = (from mem in
context.Members.Include(m =>
m.MemberProjects.Select(mp => mp.Project))
where mem.MemberId == memberId
select mem).FirstOrDefault();
This returns a Member entity, with a set of MemberProjects that have a Project child. I would like to limit the MemberProjects to only those for which the Project child has a property
ProjectIdParent == null.
One of my failed attempts might make the intent clearer:
var member = (from mem in context.Members
.Include(m => m.MemberProjects
.Where(mp =>
mp.Project.ProjectIdParent == null)
.Select(proj => proj.Project))
where mem.MemberId == memberId
select mem).FirstOrDefault();
This of course complains of an invalid Include expression because of the Where clause.
Any thoughts on how to do this would be great :)
DISCLAIMER: I havent tested this. This is just an idea. If you let me know the results, I will update this accordingly. (Skip to the update part for the tested solutions)
var member = (from mps in context.MemberProjects
.Include(m => m.Members)
.Include(m => m.Projects)
where mps.Project.ProjectIdParent == null
select mps)
.FirstOrDefault(mprojs => mprojs.Member.MemberId == memberId);
I'd also analyze the queries using something like EFProfiler to make sure the generated queries dont leave the realm of sanity.
You can also take a look at this post by Jimmy Bogard on Many to Many relationships with ORMs.
Update
I came up with multiple tested solutions for this with EF 6.1.3. My Edmx looked like below:
The setup data is like below:
I was able to run code below to get the MemberFive correctly
var member = context.Members.FirstOrDefault
(m => m.MemberId == memberId
&& m.Projects.Any(p => p.ProjectParentId == null));
The generated SQL looked like this:
SELECT TOP (1) [Extent1].[MemberId] AS [MemberId],
[Extent1].[MemberName] AS [MemberName]
FROM [dbo].[Members] AS [Extent1]
WHERE ([Extent1].[MemberId] = 1)
AND (EXISTS (SELECT 1 AS [C1]
FROM (SELECT [MemberProjects].[MemberId] AS [MemberId],
[MemberProjects].[ProjectId] AS [ProjectId]
FROM [dbo].[MemberProjects] AS [MemberProjects])
AS [Extent2]
INNER JOIN [dbo].[Projects] AS [Extent3]
ON [Extent3].[ProjectId] = [Extent2].[ProjectId]
WHERE ([Extent1].[MemberId] = [Extent2].[MemberId])
AND ([Extent3].[ProjectParentId] IS NULL)))
If you dont like the generated query you can use this:
var memberQuery = #"Select M.* from Members M
inner join MemberProjects MP on M.MemberId = Mp.ProjectId
inner join Projects P on MP.ProjectId = P.ProjectId
where M.MemberId = #MemberId and P.ProjectParentId is NULL";
var memberParams = new[]
{
new SqlParameter("#MemberId", 1)
};
var member3 = context.Members.SqlQuery(memberQuery, memberParams)
.FirstOrDefault();
The later consistently returned under 20ms vs the other one hovered around 60ms (if that matters to you).
I hope this helps.

Many to Many EF LINQ

I have a database that has the following tables:
dbo.Administrator
dbo.Application
dbo.AdminApplication
dbo.Proficiency
dbo.ProficiencyLevel
Administrators contain 1 to many Applications. Application contains many administrators
Applications contain 1 to many Proficiency(s)
Proficiency contains 1 to many ProficiencyLevels
Using EF Code First, the AdminApplication is not mapped as an entity and this is what is causing me issues. What I want to answer is the following:
"Return all the ProficiencyLevels of the Administrator named "danhickman".
In SQL, the query would look like this:
Select * from dbo.ProficiencyLevel pl
inner join dbo.Proficiency p on p.Id = pl.ProficiencyId
inner join dbo.Application a on a.Id = p.ApplicationId
inner join dbo.AdminApplication aa on aa.ApplicationId = a.Id
inner join dbo.Administrator ad on ad.Id = aa.AdministratorId
where ad.Name = 'danhickman'
I solved this with the following C# code:
public IQueryable<LobGame.Model.ProficiencyLevel> GetAllByAdminName(string administratorName)
{
var context = this.DbContext as LobGameDbContext;
var admin = context.Administrators.Include(i => i.Applications).Include("Applications.Proficiencies").Include("Applications.Proficiencies.ProficiencyLevels").Single(o => o.Name == administratorName);
List<LobGame.Model.ProficiencyLevel> list = new List<ProficiencyLevel>();
foreach (var app in admin.Applications)
{
foreach (var prof in app.Proficiencies)
{
list.AddRange(prof.ProficiencyLevels);
}
}
return list.AsQueryable();
}
It bugs me that I have to foreach and add to a list. I was unable to figure out a way to do this in a single LINQ statement. any thoughts?
Another option using query syntax. This uses SelectMany under the covers.
var queryableList =
from admin in context.Administrators
where admin.Name = administratorName
from app in admin.Applications
from proficiency in app.Proficiencies
from level in proficiency.ProficiencyLevels
select level;
Note: this will be an IQueryable, so you don't need the .ToList().AsQueryable().
return context.Administrators
.Single(o => o.Name == administratorName)
.Applications
.SelectMany(app => app.Proficiencies)
.SelectMany(prof => prof.ProficiencyLevels)
.ToList()
.AsQueryable();
Use SelectMany():
var queryableList =
context.Administrators.Single(o => o.Name.Equals(administratorName))
.SelectMany(adm => adm.Applications.Select(app => app.Proficiencies.SelectMany(prof => prof.ProficiencyLevels))).ToList().AsQueryable();

Entity Framework 4 - What is the syntax for joining 2 tables then paging them?

I have the following linq-to-entities query with 2 joined tables that I would like to add pagination to:
IQueryable<ProductInventory> data = from inventory in objContext.ProductInventory
join variant in objContext.Variants
on inventory.VariantId equals variant.id
where inventory.ProductId == productId
where inventory.StoreId == storeId
orderby variant.SortOrder
select inventory;
I realize I need to use the .Join() extension method and then call .OrderBy().Skip().Take() to do this, I am just gettting tripped up on the syntax of Join() and can't seem to find any examples (either online or in books).
NOTE: The reason I am joining the tables is to do the sorting. If there is a better way to sort based on a value in a related table than join, please include it in your answer.
2 Possible Solutions
I guess this one is just a matter of readability, but both of these will work and are semantically identical.
1
IQueryable<ProductInventory> data = objContext.ProductInventory
.Where(y => y.ProductId == productId)
.Where(y => y.StoreId == storeId)
.Join(objContext.Variants,
pi => pi.VariantId,
v => v.id,
(pi, v) => new { Inventory = pi, Variant = v })
.OrderBy(y => y.Variant.SortOrder)
.Skip(skip)
.Take(take)
.Select(x => x.Inventory);
2
var query = from inventory in objContext.ProductInventory
where inventory.ProductId == productId
where inventory.StoreId == storeId
join variant in objContext.Variants
on inventory.VariantId equals variant.id
orderby variant.SortOrder
select inventory;
var paged = query.Skip(skip).Take(take);
Kudos to Khumesh and Pravin for helping with this. Thanks to the rest for contributing.
Define the join in your mapping, and then use it. You really don't get anything by using the Join method - instead, use the Include method. It's much nicer.
var data = objContext.ProductInventory.Include("Variant")
.Where(i => i.ProductId == productId && i.StoreId == storeId)
.OrderBy(j => j.Variant.SortOrder)
.Skip(x)
.Take(y);
Add following line to your query
var pagedQuery = data.Skip(PageIndex * PageSize).Take(PageSize);
The data variable is IQueryable, so you can put add skip & take method on it. And if you have relationship between Product & Variant, you donot really require to have join explicitly, you can refer the variant something like this
IQueryable<ProductInventory> data =
from inventory in objContext.ProductInventory
where inventory.ProductId == productId && inventory.StoreId == storeId
orderby inventory.variant.SortOrder
select new()
{
property1 = inventory.Variant.VariantId,
//rest of the properties go here
}
pagedQuery = data.Skip(PageIndex * PageSize).Take(PageSize);
My answer here based on the answer that is marked as true
but here I add a new best practice of the code above
var data= (from c in db.Categorie.AsQueryable().Join(db.CategoryMap,
cat=> cat.CategoryId, catmap => catmap.ChildCategoryId,
cat, catmap) => new { Category = cat, CategoryMap = catmap })
select (c => c.Category)
this is the best practice to use the Linq to entity because when you add AsQueryable() to your code; system will converts a generic System.Collections.Generic.IEnumerable to a generic System.Linq.IQueryable which is better for .Net engine to build this query at run time
thank you Mr. Khumesh Kumawat
You would simply use your Skip(itemsInPage * pageNo).Take(itemsInPage) to do paging.

How do I merge two LINQ statements into one to perform a list2.Except(list1)?

Currently, I have the following LINQ queries. How can I merge the two queries into one. Basically, write a LINQ query to bring back the results I'd get from
IEnumerable<int> deltaList = people2010.Except(allPeople);
except in a single query.
var people2010 = Contacts.Where(x => x.Contractors
.Any(d => d.ContractorsStatusTrackings
.Any(date => date.StatusDate.Year >= 2010)))
.Select(x => x.ContactID);
var allPeople = Contacts.Where(x => x.Contractors
.Any(m => m.ContactID == x.ContactID))
.Select(x=> x.ContactID);
Thanks!
Why can you not just do Except as you are doing? Don't forget that your people2010 and allPeople variables are just queries - they're not the data. Why not just use them as they are?
If that's not acceptable for some reason, please give us more information - such as whether this is in LINQ to Object, LINQ to SQL etc, and what's wrong with just using Except.
It sounds like you're just looking for a more elegant way to write your query. I believe that this is a more elegant way to write your combined queries:
var deltaList =
from contact in Contacts
let contractors = contact.Contractors
where contractors.Any(ctor => ctor.ContractorStatusTrackings
.Any(date => date.StatusDate.Year >= 2010))
&& !contractors.Any(m => m.ContactID == contact.ContactID)
select contact.ContactID

Groupby and where clause in Linq

I am a newbie to Linq. I am trying to write a linq query to get a min value from a set of records. I need to use groupby, where , select and min function in the same query but i am having issues when using group by clause. here is the query I wrote
var data =newTrips.groupby (x => x.TripPath.TripPathLink.Link.Road.Name)
.Where(x => x.TripPath.PathNumber == pathnum)
.Select(x => x.TripPath.TripPathLink.Link.Speed).Min();
I am not able to use group by and where together it keeps giving error .
My query should
Select all the values.
filter it through the where clause (pathnum).
Groupby the road Name
finally get the min value.
can some one tell me what i am doing wrong and how to achieve the desired result.
Thanks,
Pawan
It's a little tricky not knowing the relationships between the data, but I think (without trying it) that this should give you want you want -- the minimum speed per road by name. Note that it will result in a collection of anonymous objects with Name and Speed properties.
var data = newTrips.Where(x => x.TripPath.PathNumber == pathnum)
.Select(x => x.TripPath.TripPathLink.Link)
.GroupBy(x => x.Road.Name)
.Select(g => new { Name = g.Key, Speed = g.Min(l => l.Speed) } );
Since I think you want the Trip which has the minimum speed, rather than the speed, and I'm assuming a different data structure, I'll add to tvanfosson's answer:
var pathnum = 1;
var trips = from trip in newTrips
where trip.TripPath.PathNumber == pathnum
group trip by trip.TripPath.TripPathLink.Link.Road.Name into g
let minSpeed = g.Min(t => t.TripPath.TripPathLink.Link.Speed)
select new {
Name = g.Key,
Trip = g.Single(t => t.TripPath.TripPathLink.Link.Speed == minSpeed) };
foreach (var t in trips)
{
Console.WriteLine("Name = {0}, TripId = {1}", t.Name, t.Trip.TripId);
}

Resources