Linq to EF, I'm using asp.net 4, EF 4 and C#.
Here are two ways I came up with to query my data. Ways A and C are working fine. B however needs to implement and additional WHERE statement (as "where c.ModeContent == "NA").
My question is:
Regarding this kind of join (outer join, I suppose) what is the best approach in term of performance?
Could you show me some code to implement additional WHERE statement in B?
Any way to improve this code?
Thanks for your time! :-)
// A
var queryContents = from c in context.CmsContents
where c.ModeContent == "NA" &&
!(from o in context.CmsContentsAssignedToes select o.ContentId)
.Contains(c.ContentId)
select c;
// B - I need to implent where c.ModeContent == "NA"
var result01 = from c in context.CmsContents
join d in context.CmsContentsAssignedToes on c.ContentId equals d.ContentId into g
where !g.Any()
select c;
// C
var result02 = context.CmsContents.Where(x => x.ModeContent == "NA").Where(item1 => context.CmsContentsAssignedToes.All(item2 => item1.ContentId != item2.ContentId));
Regarding query B you can apply the condition like this:
var result01 = from c in context.CmsContents where c.ModeContent == "NA"
join d in context.CmsContentsAssignedToes on c.ContentId equals d.ContentId into g
where !g.Any()
select c;
Your query will be far more readable and maintainable (and perform at least as well) if you use your association properties instead of join:
var result = from c in context.CmsContents
where c.ModeContent == "NA"
&& !c.AssignedToes.Any()
select c;
I'm guessing that the navigation on CmsContent to CmsContentsAssignedToes is called AssignedToes. Change the name in my query if it's actually called something else.
This query can be read out loud and you know exactly what it means. The join versions you have to think about.
Related
I have three tables, Chart, ChartQuestion, ChartAnswer
A Chart has many ChartQuestions, a ChartQuestion has many ChartAnswers.
I'd like to do a linq query that gives me an object containing all of this data, so it'd be a ChartObject containing List<ChartQuestion> and each ChartQuestion contains a List<ChartAnswer>
I started with this:
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId into chartQuestions)
This seems to be the first step. However I want to include the ChartAnswers now, so I have to do another join to pull back the ChartAnswers but I don't know how to do this.
I can't do a join, and while I can do a from, I am not sure of the exact syntax.
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId into chartQuestions
from chartQuestionsSelection in chartQuestions
join chartAnswer in context.ChartAnswers on chartQuestions.ChartAnswerId equals chartAnswer.ChartAnswerId into chartAnswers // This is wrong
)
With that code above you end up as chartAnswers being separate to the chartQuestions rather than belonging to them, so I don't think it is correct.
Any idea?
joining a table destructors it i.e. you now have the row as a variable if you need it in a nested fashion then select in like this
(from chart in db.Chart
select new { //can also be your own class/record, perhaps a DTO
chart.Id,
chart.Name,
questions = (from chartQuestion in db.chartQuestion
where chart.ChartId == chartQuestion.ChartId
select new { //perhaps a dto
chartQuestion.Id,
chartQuestion.Question,
Answers =
(from chartAnswer in context.ChartAnswers
where chartAnswer.ChartAnswerId == chartQuestion.ChartAnswerId
select chartAnswer).ToList() //this is translated and not evaluated
}).ToList() //this is translated not evaluated
}).ToListAsync(cancellationToken) //this will evaluate the expression
If you need it by joins then you can group join it like this:
from m in _context.Chart
join d in _context.ChartQuestions
on m.ID equals d.ID into mdJoin
select new
{
chartId = m.ID,
chartName = "m.name",
quess = from d in mdJoin
join dd in _context.ChartAnswer
on d.Iddomain equals dd.DomainId into anJoin
select new
{
quesId = d.ID,
quesName = d.Question,
anss = anJoin
}
}
A better way: If you edit your DbConfigurations to include navigation properties of each then the code will become way simpler.
example:
db.Chart
.Include(x => x.chartQuestions)
.ThenInclude(x => x.chartAnswers)
You can search more about how to do navigation properties in EFCore, but ofcourse if the code base is large then this might not be feasible for you.
Try following :
(from chart in db.Chart
join chartQuestion in db.chartQuestion on chart.ChartId equals chartQuestion.ChartId
join chartAnswer in context.ChartAnswers on chartAnswer.ChartAnswerId equals chartQuestion.ChartAnswerId
)
Hoping that I'm just missing something obvious but here's my query
var data = (from project in _db.Projects
where project.Id == id
let primaryCategory = (from c in _db.Categories
where c.CategoryID == project.PrimaryCategory
select c.Name)
let categories = (from c in _db.ProjectCategories
join pc in _db.Projects_ProjectCategories on c.ProjectCategoryID equals pc.ProjectCategoryID
where pc.ProjectID == project.ProjectID
select c.Name)
let owner = (from o in _db.Owners
join po in _db.App_Projects_Owners on o.OwnerID equals po.OwnerID
where po.ProjectID == project.ProjectID
select new OwnerModel
{
Owner = o,
Project = project,
PrimaryCategory = primaryCategory.FirstOrDefault(),
Categories = categories.ToList()
})
select new
{
owner,
project
}).FirstOrDefault();
In there OwnerModel.Categories is a List of strings. I can't use ToList() in the query because it gives a materialization error. I've added a custom setter that takes the IQueryable, but that still makes another round trip to the database for every owner that the query returns.
So how are you supposed to assign basic lists in a subquery?
EDIT AND ANSWER (since Robert McKee lead me to the answer in his comment).
The answer is to use the group by clause like so
var data = (from project in _db.Projects
where project.Id == id
let primaryCategory = (from c in _db.Categories
where c.CategoryID == project.PrimaryCategory
select c.Name)
let categories = (from c in _db.ProjectCategories
join pc in _db.Projects_ProjectCategories on c.ProjectCategoryID equals pc.ProjectCategoryID
where pc.ProjectID == project.ProjectID
group c.Name by pc.ProjectCategoryID into x
select x.ToList())
let owner = (from o in _db.Owners
join po in _db.App_Projects_Owners on o.OwnerID equals po.OwnerID
where po.ProjectID == project.ProjectID
select new OwnerModel
{
Owner = o,
Project = project,
PrimaryCategory = primaryCategory.FirstOrDefault(),
Categories = categories
})
select new
{
owner,
project
}).FirstOrDefault();
Specifically note the bits involving
group c.Name by pc.ProjectCategoryID into x
select x.ToList()
It may seem counter-intuitive that it works like this until you dig into exactly what's going on. The method that I was calling above with categories.ToList() was trying to use the System.Collection.Generics ToList function, and that list didn't have any way to convert the expression to sql. However by using the group by clause I was creating a specialized Enumerable IGrouping and calling the ToList function on that. This function is able to be translated into a sql statement and thus doesn't throw the exception.
Learn something new every day.
Set up your navigation properties, and then your query becomes something like:
var data=db.Projects
.Include(p=>p.PrimaryCategory)
.Include(p=>p.Categories)
.Include(p=>p.Owner) // or .Include(p=>p.Owners) if projects can have multiple owners
.First(p=>p.Id == id);
As for child objects, you are looking for the group by clause or .GroupBy method.
I've been scratching my head for some hours trying to figure this one out. I need to search in several tables, using Entity Framework 6 and I've tried to make it work using joins with no luck at all. After a while, I decided to simply make it into several queries, which I'm not sure if ideal at all.
The following code works, but it's not pretty, I think (searchTerm is my search query which is a string):
// Search shareholders
var shareholders =
(from sh in ctx.FirmTypeShareholders.Where(x =>
x.CustomerID.ToString().Equals(searchTerm) ||
x.Firm_FirmType.Firm.ID.ToString().Equals(searchTerm))
join firm in ctx.Firms on sh.Firm_FirmType.FirmID equals firm.ID
select sh).ToList();
// Power plants by shareholder id
var powerPlantsByShareholder = (from sh in ctx.FirmTypeShareholders
join firm in ctx.Firms on sh.Firm_FirmType.FirmID equals firm.ID
from o in firm.Ownerships
join ppu in ctx.PowerPlantUnits on o.PowerPlantUnitID equals ppu.ID
join powerPlant in ctx.PowerPlants on ppu.PowerPlantID equals powerPlant.ID
where sh.CustomerID == 34
select new
{
Shareholder = sh,
PowerPlant = powerPlant
});
// Search persons
var persons = (from p in ctx.People.Where(x =>
x.ID.ToString().Equals(searchTerm) || x.Mobile.ToString().Equals(searchTerm) ||
x.Email.Equals(searchTerm) || x.Name.Contains(searchTerm))
select p).ToList();
// Search power plants
var powerPlants = (from pp in ctx.PowerPlants.Where(x =>
x.ID.ToString().Equals(searchTerm) || x.GSRN.ToString().Equals(searchTerm) ||
x.EDIEL.ToString().Equals(searchTerm))
select pp).ToList();
Is there a way to make this work with a single query?
Thanks in advance :-)
Hi I have the following Linq query:
(from c in new_contactsubscriptionSet
join p in new_PaymentStatusSet
on c.new_PaymentStatusId.Id equals p.new_PaymentStatusId
where (c.new_EndDate > DateTime.Now &&
c.new_EndDate <= DateTime.Now.AddDays(14)) &&
p.new_IsPaidStatus == false
select c)
It throws the following FaultException which means its checking attribute new_ispaidstatus on wrong entity. It should be checking on new_PaymentStatus rather then new_contactsubscription
FaultException
'new_contactsubscription' entity doesn't contain attribute with Name = 'new_ispaidstatus'.
If I use the following queries its working fine:
(from c in new_contactsubscriptionSet
join p in new_PaymentStatusSet
on c.new_PaymentStatusId.Id equals p.new_PaymentStatusId
where p.new_IsPaidStatus == false
select c)
OR
(from c in new_contactsubscriptionSet
join p in new_PaymentStatusSet
on c.new_PaymentStatusId.Id equals p.new_PaymentStatusId
where (c.new_EndDate > DateTime.Now &&
c.new_EndDate <= DateTime.Now.AddDays(14))
select c)
Looks like something is wrong with the Where clause. Can anyone help me out to fix this query.
Thanks in Advance
You'll need a different where for each entity.
(from c in new_contactsubscriptionSet
join p in new_PaymentStatusSet
on c.new_PaymentStatusId.Id equals p.new_PaymentStatusId
where (c.new_EndDate > DateTime.Now && c.new_EndDate <= DateTime.Now.AddDays(14))
where p.new_IsPaidStatus == false
select c)
This is due to the way Microsoft maps the Linq query to a Query Expression. It attempts to map a where into it's only filter criteria, but these are applied on an entity to entity basis. So it is determining the names of all the attributes used, and creating a filter for it against the first expression it evaluates' entity.
With the multiple where's, it'll update the filter of the second Linked Entity rather than blindly adding it to the first.
I have a few Linq queries. Semantically, they are
a join b join c join d where filter1(a) && filter2(c) && filter3(d)
a join b join c where filter1(a) && filter2(c)
a join b join c join e where filter1(a) && filter2(c) && filter4(e)
...
I want to be able to factor out the shared part:
a join b join c where filter1(a) && filter2(c)
and dynamically append join d and filter3(d)
Is there a way to do this? I am already using the Predicate Builder to dynamically build conditionals (filters).
EDIT: I am using Linq-to-SQL.
EDIT: The base query looks like:
from a in As.AsExpandable()
join b in Bs on a.Id equals b.PId
join c in Cs on b.Id equals c.PId
where filter1(a) && filter2(b) && filter3(c)
select new A { ... }
filters are predicates in Predicate Builder. The type of the query is IQueryable<A>.
Next, I'd like to join this with d
from a in BaseQuery()
join d in D on a.Id equals d.PId
Currently join d .. causes a compilation error:
The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to Join
Your example is a bit vague, but it is easy to create a method that returns an IQueryable<T> and reuse that method, if that’s what you mean. Here is an example:
// Reusable method
public IQueryable<SomeObject> GetSomeObjectsByFilter(Context c)
{
return
from someObject in context.SomeObjects
where c.B.A.Amount < 1000
where c.Roles.Contains(r => r.Name == "Admin")
select someObject;
}
You can reuse this method in other places like this:
var q =
from c in GetSomeObjectsByFilter(context)
where !c.D.Contains(d => d.Items.Any(i => i.Value > 100))
select c;
Because the way IQueryable works, only the final query (the collection that you start iterating) will trigger a call to the database, which allows you to build a highly maintainable system by reusing business logic that gets effectively executed inside the database, whiteout the loss of any performance.
I do this all the time and it improves the maintainability of my code big time. It works no matter which O/RM tool you run, because there is no difference in Queryable<T> composition, between writing the query in one peace, or splitting it out to different methods.
Note that you do sometimes need some smart transformations to get the duplicate parts in a single method. Things that might help are returning grouped sets, and returning a set of a different type, than what you think you need. This sounds a bit vaque, but just post a question here at SO when you have problems splitting up a method. There are enough people here that can help you with that.
I can answer half your question easily. Linq makes it simple to append .where clauses to an existing query.
Example:
var x = db.Table1.where(w => w.field1 == nTestValue);
x = x.where(w => w.field2 == nTestValue2);
I believe you can do the joins as well but I have to go find an example in some old code. I'll look if nobody else jumps in with it soon.