Linq include with where clause - linq

Hey so I've got the situation where I'm pulling a client back from the database and including all the case studies with it by way of an include
return (from c in db.Clients.Include("CaseStudies")
where c.Id == clientId
select c).First();
but what I want to do now is and a where clause on the included casestudies so that it only returns the case studies where deleted = false
sort of like this
return (from c in db.Clients.Include("CaseStudies")
where c.Id == clientId
&& c.CaseStudy.Deleted == false
select c).First();
But this doesn't work :( any ideas

Conditional includes are not supported out-of-the-box in EF v1.0. But Alex James has a bit hacky workaround for that explained well here: http://blogs.msdn.com/alexj/archive/2009/10/13/tip-37-how-to-do-a-conditional-include.aspx
var dbquery =
from c in db.Clients
where c.Id == clientID
select new {
client = c,
caseStudies = from cs in c.CaseStudy
where cs.Deleted==false
select cs
};
return dbquery
.AsEnumerable()
.Select(c => c.client);
Also, I haven't succeeded to make this workaround work with many-to-many relationships.

You can return a similar group of records this way, the GroupBy is going to make the enumeration different, but its not difficult.
CaseStudies.Include("Client")
.Where(c => !c.Deleted && c.Client.ID == ClientID)
.GroupBy(c => c.Client.ID);

One option is to perform a query on your results, like this:
var results = (from c in db.Clients.Include("CaseStudies")
where c.Id == clientId
select c).First();
results.CaseStudies = (from c in results.CaseStudies
where c.Deleted == false
select c).ToList();
Or of course you can use a lambda expression:
var results = db.Clients
.Include(c => c.CaseStudies)
.Where(c => c.ID == clientId).First();
results.CaseStudies = results.CaseStudies.Where(c => !c.Deleted).ToList();

Related

Left joining a table's id with the maximum id from a subquery

I am using .NET core and trying to write a Linq query to list Cases. I have 3 tables that I am trying to join together:
Cases - Includes CaseID and CaseStatusID
CaseStatuses - Includes CaseStatusID (a Case has one of these)
CaseStatusHistory - Includes CaseStatusHistoryID, CaseID, CaseStatusID (a Case has many of these).
I can join Cases and CaseStatuses easily on CaseStatusID but am not sure how to join CaseStatusHistory on CaseStatusHistoryID = a subquery selecting the MAX(CaseStatusHistoryID) WHERE CaseID and CaseStatusID matches. I could even select the CaseStatusHitory later in my results but am not sure how.
This is what I need to convert
SELECT
c.CaseID,
csh.DateTimeAdded
FROM dbo.Cases c
JOIN dbo.CaseStatuses cs
ON c.CaseStatusID = cs.CaseStatusID
LEFT JOIN dbo.CaseStatusHistory csh
ON csh.CaseStatusHistoryID =
(
SELECT MAX(CaseStatusHistoryID)
FROM dbo.CaseStatusHistory
WHERE CaseID = c.CaseID
AND cs.CaseStatusID = c.CaseStatusID
)
This is what I have so far in Linq
IQueryable<CasesViewModel> objs =
from c in _db.Cases
from cs in _db.CaseStatuses.Where(cs => cs.CaseStatusId == c.CaseStatusId.Value).DefaultIfEmpty()
select new CasesViewModel
{
CaseID = c.CaseId,
CaseStatus = cs.CaseStatus
//StatusChangeDate = csh.DateTimeAdded
};
I need to add something like this
From csh In CaseStatusHistories
Where
(
From _csh In CaseStatusHistories
Where _csh.CaseID = c.CaseID AndAlso _csh.CaseStatusID = c.CaseStatusID
Select _csh.CaseStatusHistoryID
).Max = csh.CaseStatusHistoryID
Here is what I have so far but it does not return any result - just seems to time out.
IQueryable<CasesViewModel> objs =
from c in _db.Cases
from cs in _db.CaseStatuses.Where(cs => cs.CaseStatusId == c.CaseStatusId.Value).DefaultIfEmpty()
from rg in _db.RepairingGarages.Where(rg => rg.RepairingGarageId == c.RepairingGarageId.Value).DefaultIfEmpty()
from rgb in _db.Businesses.Where(rgb => rgb.BusinessId == rg.BusinessId.Value).DefaultIfEmpty()
from ec in _db.EngineeringCompanies.Where(ec => ec.EngineeringCompanyId == c.EngineeringCompanyId.Value).DefaultIfEmpty()
from ecb in _db.Businesses.Where(ecb => ecb.BusinessId == ec.BusinessId.Value).DefaultIfEmpty()
from csh in _db.CaseStatusHistory.Where(csh => csh.CaseStatusHistoryId ==
(
from _csh in _db.CaseStatusHistory
where _csh.CaseId == c.CaseId && _csh.CaseStatusId == c.CaseStatusId.Value
select _csh.CaseStatusHistoryId
).Max()).DefaultIfEmpty()
select new CasesViewModel
{
CaseID = c.CaseId,
RepairingGarage = rgb.BusinessName,
Engineer = ecb.BusinessName,
CaseStatus = cs.CaseStatus,
StatusChangeDate = csh.DateTimeAdded
};
I just need to get the StatusChangeDate which is the DateTimeAdded in the CaseStatusHistory table. Cases can be at the same status more than once, so I just need the DateTimeAdded with the highest CaseStatusHistoryID for a particular Case. Thank you very much for any help.
This appears to answer my question
IQueryable<CasesViewModel> objs =
from c in _db.Cases
from cs in _db.CaseStatuses.Where(cs => cs.CaseStatusId == c.CaseStatusId.Value).DefaultIfEmpty()
from rg in _db.RepairingGarages.Where(rg => rg.RepairingGarageId == c.RepairingGarageId.Value).DefaultIfEmpty()
from rgb in _db.Businesses.Where(rgb => rgb.BusinessId == rg.BusinessId.Value).DefaultIfEmpty()
from ec in _db.EngineeringCompanies.Where(ec => ec.EngineeringCompanyId == c.EngineeringCompanyId.Value).DefaultIfEmpty()
from ecb in _db.Businesses.Where(ecb => ecb.BusinessId == ec.BusinessId.Value).DefaultIfEmpty()
select new CasesViewModel
{
CaseID = c.CaseId,
RepairingGarage = rgb.BusinessName,
Engineer = ecb.BusinessName,
CaseStatus = cs.CaseStatus,
StatusChangeDate =
(
from _csh in _db.CaseStatusHistory
where _csh.CaseId == c.CaseId && _csh.CaseStatusId == cs.CaseStatusId
orderby _csh.CaseStatusHistoryId descending
select _csh.DateTimeAdded
).FirstOrDefault()
};
The only trouble with not joining is that I am not sure if I can sort by this date now. Any better ideas? Thank you.

LinQ to SQL and where is the choice for all database

I have the source code like this:
var res = from s in Splitting
join c in Customer on s.CustomerId equals c.Id
where c.Id == customrId
&& c.CompanyId == companyId
select s;
When reviewing code, one member said that my code applies for only one SQL db, and advised me to use LinQ to Entity with Join so that it will work for all databases.
I don't understand, I think, even with other db, we will add it to Entity Framework. And the code below will work correct too, right?
Please advise.
There are two ways you can write your LINQ.
1.LINQ Query Expressions (query-syntax) (Which you have done)
var res = from s in Splitting
join c in Customer on s.CustomerId equals c.Id
where c.Id == customrId
&& c.CompanyId == companyId
select s;
2.Another is LINQ query extension methods (dot-syntax)
var res = Splitting.Join(Customer,
sp => sp.CustomerId,
cu => cu.Id,
(sp, cu) => new { sp, cu })
.Where(s => s.cu.Id == customrId && s.cu.CompanyId == companId)
.Select(s => s.sp);
For joins, I strongly prefer query-syntax.There are details that
query-syntax hides that can make it well worth embracing with the improvement to readability it brings.However query-syntax is somewhat
more limited than dot-syntax in other aspects.
dot-syntax is more concise but performing multiple table joins is a nightmare.The flip side is that there are a number of LINQ
operations that only exist within the dot-syntax: Single(),
First(), Count() etc.For these limitation of query-syntax you can use
dot-syntax.
N.B : At compile time, all are converted to Standard Query.

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.

Resources