Select category tree in Entity Framework - c#

I have a Category table with a tree structure (Id,MasterId)
I'd like to select all products that belong to a Category and all Child Categories.
Today I use this SQL Query which works, but I'd like to add pagination and that would be easier with a pure LINQ query. I use Entity Framework 4.
#Count int = 100,
#CategoryId int
with mq as
(
select c.Id as parent, c.Id as child
from dbo.Categories c
where c.Id = #CategoryId
union all
select q.child, c.Id
from mq q
inner join dbo.Categories c on q.child = c.MasterId
)
select top (#Count) P.* from Products P
inner join ProductToCategory PC ON(PC.ProductId = P.Id)
where PC.CategoryId in (
select child from mq
)
and P.PublishStatus = 1
order by P.PublishedDate DESC;
Any ideas how to get a nice LINQ query on this with pagination (current page, number of products per page, total product count)?

This is recursive / hiearchical query with table expression. EF does not provide support for such queries. If you want to receive data by single roundtrip to DB you must wrap it in stored procedure and import that procedure to your entity framework model.
Paging in SQL is also possible when using table expressions and ROW_NUMBER().

there is an idea. i haven't tested it, so dont blame if it doesn't work :P
var ids = context.TreeItems.Where(x => x.Id == parentId).Select(x => (int?)x.Id);
var tmp = ids;
while (true)
{
IQueryable<int?> localIds = tmp;
var subIds = context.TreeItems.Where(x => ids.Contains(x.ParentId)).Select(x => (int?)x.Id);
if (subIds.Any())
{
tmp = subIds;
ids = ids.Union(subIds);
}
else
break;
}
var allSubItems = context.TreeItems.Where(x => ids.Contains(x.Id));

Related

How can I turn SQL query that joins two columns and groups by count of one column and a column of each joined table into LINQ?

In my database, each URI has associated tags (Tag table) and each pageview (PageView table) is associated with someone viewing a particular page. I want to return a list of URIs that have the same tags as a given URI, by count of each URI that shares those tag(s). My SQL query looks like this:
select count(URI) as 'Count', p.URI, t.Name
from tracking.PageView as p
inner join Tracking.Tag as t on p.ID = t.PageViewID
where t.name in
(select t.Name
from tracking.PageView as p
inner join Tracking.Tag as t on p.ID = t.PageViewID
where p.URI = 'URI WE WANT TAGS OF'
)
and p.uri like '%/articles/%'
group by p.URI , t.name
order by Count desc
My apologies if the description is too vague for the query or if the query itself is rough. It was just the first one that worked. I've tried to separate the subquery into a variable and select values in that subquery, but it's been some time since I've used LINQ and I'm spinning wheels at this point.
The following is pretty much an exact translation of your current SQL query, which should get you started.
from p in tracking.PageView
join t in Tracking.Tag on p.ID equals t.PageViewID
where p.uri.Contains("/articles/")
&& (
from p2 in tracking.PageView
join t2 in Tracking.Tag on p2.ID equals t2.PageViewID
where p2.URI == "URI WE WANT TAGS OF"
select t2.name
).Contains(t.name)
group new { p, t } by new { p.URI, t.name } into g
orderby g.Count() descending
select new {
Count = g.Count(),
g.Key.URI,
g.Key.Name
}

EF Core 3.0 - Convert SQL to LINQ

The example given in the blog has the following
from e in s.StudentCourseEnrollments where courseIDs.Contains(e.Course.CourseID) select e
The contains logic will not work when we are looking for an exact match. If a student has enrolled for 6 courses (ex : 1,2,3,4,5,6) and the requested list contains 5 (ex: 1,2,3,4,5) the query will return a match when it should not. The other way works well when the student has enrolled in a subset of the requested list.
Below solution works but need help to convert the below sql to LINQ (EF Core 3.0) ?
Create TABLE dbo.Enrollments (StudentId INT NOT NULL, CourseId INT NOT NULL)
insert into dbo.Enrollments values (1,1)
insert into dbo.Enrollments values (1,2)
insert into dbo.Enrollments values (1,3)
insert into dbo.Enrollments values (1,4)
insert into dbo.Enrollments values (1,5)
insert into dbo.Enrollments values (1,6)
DECLARE #TempCourses TABLE
(
CourseId INT
);
INSERT INTO #TempCourses (CourseId) VALUES (1), (2), (3),(4),(5);
SELECT t.StudentId
FROM
(
SELECT StudentId, cnt=COUNT(*)
FROM dbo.Enrollments
GROUP BY StudentId
) kc
INNER JOIN
(
SELECT cnt=COUNT(*)
FROM #TempCourses
) nc ON nc.cnt = kc.cnt
JOIN dbo.Enrollments t ON t.StudentId = kc.StudentId
JOIN #TempCourses n ON n.CourseId = t.CourseId
GROUP BY t.StudentId
HAVING COUNT(*) = MIN(nc.cnt);
drop table dbo.Enrollments
db<>Fiddle
I don't know about the SQL query, but the EF Core 3.0 LINQ query for the same task is something like this:
var matchIds = new[] { 1, 2, 3, 4, 5 }.AsEnumerable();
var query = dbContext.Students
.Where(s => s.Enrollments.All(e => matchIds.Contains(e.CourseId))
&& s.Enrollments.Count() == matchIds.Count());
The main matching job is done with All subquery. Unfortunately that's not enough for the case when related link records are more than the matching ids, so additional counts comparison solves that.
You can achieve it with a simple way like this, live demo here
Let's say that you've got the list of enrollments by this way
var enrollments = from s in dc.Students
from c in s.Courses
select new { StudentID = s.StudentID, CourseID = c.CourseID };
Then get the result by this way
var groupedEnrollment = enrollments.GroupBy(p => p.StudentId)
.Select(g => new
{
StudentId = g.Key,
Courses = g.Select(p => p.CourseId).ToArray()
});
var result = groupedEnrollment.Where(g =>
g.Courses.Length == courses.Length &&
g.Courses.Intersect(courses).Count() == courses.Length);

How to get average of Categories if SubCategories are referenced in Result table?

I am having problems in writing a LINQ for calculating average value based on not directly referenced column.
Provide background and tell us what you've already tried.
I have following tables, now I want average marks based on category and subcategory, But fetching it through TopicId(FK) in Result table is going to be a big task.
(I tried and couldn't figure out how to do it)
Image having tables
How Can I get average marks for Category and subcategory?
This is the SQL query that you need:
select
c.Id as CategoryId,
c.CategoryName,
sc.Id as SubCategoryId,
sc.SubCategoryName,
AVG(r.Marks) as Average
from Result r
join Topic t on r.TopicId = t.Id
join SubCategory sc on t.SubCategoryId = sc.Id
join Category c on sc.CategoryId = c.Id
group by c.Id, c.CategoryName, sc.Id, sc.SubCategoryName
And this is the same with LINQ query syntax:
using (var db = new TopicContext())
{
// build the query
var query =
from r in db.Result
join t in db.Topic on r.TopicId equals t.Id
join sc in db.SubCategory on t.SubCategoryId equals sc.Id
join c in db.Category on sc.CategoryId equals c.Id
group r by new { c.Id, c.CategoryName, SubCategoryId = sc.Id, sc.SubCategoryName } into gr
select new
{
CategoryId = gr.Key.Id,
CategoryName = gr.Key.CategoryName,
SubCategoryId = gr.Key.SubCategoryId,
SubCategoryName = gr.Key.SubCategoryName,
Average = gr.Average(x => x.Marks)
};
// ToList() method executes the query, so we get the result on that line of code
var result = query.ToList();
}
If you have an existing database you can use EF Core to generate your own dbContext (in my example this is TopicContext class) with the help of Scaffold-DbContext command. You can find an example of this command on that page. To use this command you should install Microsoft.EntityFrameworkCore.Tools nuget package along with Microsoft.EntityFrameworkCore.SqlServer.

EF (LINQ to SQL) - How to query?

I have the concept of a document that has keyword/s. EF abstracted out the document-keyword joining table to an association.
The structure looks like this
Document: ID (PK)
Document_Keyword: DocumentID (PK), Keyword (PK)
Keyword: Keyword (PK)
I have the requirement to return a list of documents where they contain ALL keywords in a string[]
If I was doing this in SQL it would be similar to below
with t as (
select 'keyword1' KEYWORD union
select 'keyword2'
)
select DocumentID,count(*) from [dbo].[Document_Keyword] p
inner join t on p.KEYWORD = t.KEYWORD
group by DocumentID
having count(*) = (select count(*) from t)
Im struggling to form a linq query that will give me the same result.
I have tried the following LINQ statement however it does returns documents that contain 1 or more of the keywords in the array. I require that documents are only returned if ALL keywords match.
var query = (from k in db.KEYWORD
from b in k.DOCUMENT
join q in arrKeywords //array of string[]
on k.KEYWORD equals q
select new Document()
{
Filename = b.FILENAME,
Description = b.TITLE
});
Any ideas?
Cheers
Jeremy
If I get you well you want entries of which all keywords match exactly, i.e. it doesn't have any other keywords. A way too achieve this is
var kwc = arrKeywords.Count();
var query = from k in db.KEYWORD
let kw = k.DOCUMENT.Select(d => d.KEYWORD)
where kw.All(kw1 => arrKeywords.Contains(kw1))
&& kw.Count() == kwc;
The generated query is still much longer than a hand-coded one would be, but I think the database's query optimizer should be able to handle this.

NHibernate join subquery

I have 3 simple tables (entities): Page (id), Control (id, page_id) and Setting (id, control_id).
So the structure is Page->Control->Setting.
I have query:
SELECT
p.*
,c.*
,s.*
FROM #page p
LEFT JOIN ( SELECT * FROM #control WHERE id = #controlid) c ON p.id = c.page_id
LEFT JOIN ( SELECT * FROM #settings WHERE id = #settingid) s ON s.id = c.page_id
WHERE
p.id = #pageid
How to build NHibernate construction to generate the same query?
If your datamodel is correct you would only have to do something like this
session.QueryOver<Page>()
.Fetch(x => x.Control)
.Eager.List<Page>();
Depending on a lot of thins you may have to implement ThenFetch.

Categories

Resources