EF Core - Count from a specific column - ef-code-first

I almost have my EF Core query working... This is the SQL getting produced (notice the Count(*):
SELECT [u].[Key], [u].[Url], [u].[CreatedBy], [u].[CreatedOn], COUNT(*) AS [Clicks]
FROM [URLs] AS [u]
LEFT JOIN [OwnerUrls] AS [o] ON [u].[Key] = [o].[ShortUrlKey]
LEFT JOIN [Clicks] AS [c] ON [u].[Key] = [c].[ShortUrlKey]
GROUP BY [u].[Key], [u].[Url], [u].[CreatedBy], [u].[CreatedOn]
What I need is (have Count look at a specific column/table)
SELECT [u].[Key], [u].[Url], [u].[CreatedBy], [u].[CreatedOn], COUNT(c.ID) AS [Clicks]
FROM [URLs] AS [u]
LEFT JOIN [OwnerUrls] AS [o] ON [u].[Key] = [o].[ShortUrlKey]
LEFT JOIN [Clicks] AS [c] ON [u].[Key] = [c].[ShortUrlKey]
GROUP BY [u].[Key], [u].[Url], [u].[CreatedBy], [u].[CreatedOn]
Here is the EF Query that I'm using...
query = (from u in db.URLs
join ou in db.OwnerUrls on u.Key equals ou.ShortUrlKey into urlOwners
from subSet in urlOwners.DefaultIfEmpty()
join c in db.Clicks on u.Key equals c.ShortUrlKey into urlClicks
from subClicks in urlClicks.DefaultIfEmpty()
group subClicks by new { u.Key, u.Url, u.CreatedBy, u.CreatedOn } into g
select new ShortURL()
{
Key = g.Key.Key,
Url = g.Key.Url,
CreatedBy = g.Key.CreatedBy,
CreatedOn = g.Key.CreatedOn,
Clicks = g.Count()
});
I've tried changing the g.Count() to g.Select(x=>x.Id).Count() and that just causes EF Core to barf and complain about client side evaluation vs server side evaluation etc..
I should mention that the reason I'm joining the first model (OwnerUrls) is to support a where clause that I didn't include here...
Thanks!

I'm not a EF developer, but have worked with SQL Server for a while now. In SQL Server i would use COUNT(DISTINCT c.ID) to eliminate any duplicates you might get from JOINS.
If duplicates are impossible due to the model the COUNT(*) shoud be sufficient.
Maybe this might help:
https://entityframeworkcore.com/knowledge-base/51892585/linq-select-distinct-count-performed-in-memory

Related

Two queries in one statment

I want in put two queries in one statement how can i do that in this state?
1
stmt = `SELECT Comments.*, Users.username,Users.avatar from Users
INNER JOIN Comments ON Comments.users_id =Users.users_id
WHERE Comments.post_id= 1`
2
`SELECT COUNT(*) comment FROM Comments WHERE Comments.post_id= 1`;
Cross join the 1st query to the 2nd:
SELECT c.*, u.username, u.avatar, t.counter
FROM Users u INNER JOIN Comments c
ON c.users_id = u.users_id
CROSS JOIN (SELECT COUNT(*) counter FROM Comments WHERE post_id = 1) t
WHERE c.post_id = 1
I don't know much about SQLite, but in SQL Server you can use ";" to use multiple queries.
Maybe it does work for SQLite.
You can use GROUP BY on comments table in a way:
'SELECT COUNT(Comments.<id>), Comments.*, Users.username,Users.avatar
from Users INNER JOIN Comments ON Comments.users_id =Users.users_id
WHERE Comments.post_id = 1 GROUP BY Comments.<id>';
*This syntax of GROUP BY clause follows PostgreSQL. You might need to tweak according to the syntax followed by sqlite.

Efficient joining the most recent record from another table in Entity Framework Core

I am comming to ASP .NET Core from PHP w/ MySQL.
The problem:
For the illustration, suppose the following two tables:
T: {ID, Description, FK} and States: {ID, ID_T, Time, State}. There is 1:n relationship between them (ID_T references T.ID).
I need all the records from T with some specific value of FK (lets say 1) along with the related newest record in States (if any).
In terms of SQL it can be written as:
SELECT T.ID, T.Description, COALESCE(s.State, 0) AS 'State' FROM T
LEFT JOIN (
SELECT ID_T, MAX(Time) AS 'Time'
FROM States
GROUP BY ID_T
) AS sub ON T.ID = sub.ID_T
LEFT JOIN States AS s ON T.ID = s.ID_T AND sub.Time = s.Time
WHERE FK = 1
I am struggling to write an efficient equivalent query in LINQ (or the fluent API). The best working solution I've got so far is:
from t in _context.T
where t.FK == 1
join s in _context.States on t.ID equals o.ID_T into _s
from s in _s.DefaultIfEmpty()
let x = new
{
id = t.ID,
time = s == null ? null : (DateTime?)s.Time,
state = s == null ? false : s.State
}
group x by x.id into x
select x.OrderByDescending(g => g.time).First();
When I check the resulting SQL query in the output window when executed it is just like:
SELECT [t].[ID], [t].[Description], [t].[FK], [s].[ID], [s].[ID_T], [s].[Time], [s].[State]
FROM [T] AS [t]
LEFT JOIN [States] AS [s] ON [T].[ID] = [s].[ID_T]
WHERE [t].[FK] = 1
ORDER BY [t].[ID]
Not only it selects more columns than I need (in the real scheme there are more of them). There is no grouping in the query so I suppose it selects everything from the DB (and States is going to be huge) and the grouping/filtering is happening outside the DB.
The questions:
What would you do?
Is there an efficient query in LINQ / Fluent API?
If not, what workarounds can be used?
Raw SQL ruins the concept of abstracting from a specific DB technology and its use is very clunky in current Entity Framework Core (but maybe its the best solution).
To me, this looks like a good example for using a database view - again, not really supported by Entity Framework Core (but maybe its the best solution).
What happens if you try to do a more straight forward translation to LINQ?
var latestState = from s in _context.States
group s by s.ID_T into sg
select new { ID_T = sg.Key, Time = sg.Time.Max() };
var ans = from t in _context.T
where t.FK == 1
join sub in latestState on t.ID equals sub.ID_T into subj
from sub in subj.DefaultIfEmpty()
join s in _context.States on new { t.ID, sub.Time } equals new { s.ID, s.Time } into sj
from s in sj.DefaultIfEmpty()
select new { t.ID, t.Description, State = (s == null ? 0 : s.State) };
Apparently the ?? operator will translate to COALESCE and may handle an empty table properly, so you could replace the select with:
select new { t.ID, t.Description, State = s.State ?? 0 };
OK. Reading this article (almost a year old now), Smit's comment to the original question and other sources, it seems that EF Core is not really production ready yet. It is not able to translate grouping to SQL and therefore it is performed on the client side, which may be (and in my case would be) a serious problem. It corresponds to the observed behavior (the generated SQL query does no grouping and selects everything in all groups). Trying the LINQ queries out in Linqpad it always translates to a single SQL query.
I have downgraded to EF6 following this article. It required some changes in my model's code and some queries. After changing .First() to .FirstOrDefault() in my original LINQ query it works fine and translates to a single SQL query selecting only the needed columns. The generated query is much more complex than it is needed, though.
Using a query from NetMage's answer (after small fixes), it results in a SQL query almost identical to my own original SQL query (there's only a more complex construct than COALESCE).
var latestState = from s in _context.States
group s by s.ID_T into sg
select new { ID = sg.Key, Time = sg.Time.Max() };
var ans = from t in _context.T
where t.FK == 1
join sub in latestState on t.ID equals sub.ID into subj
from sub in subj.DefaultIfEmpty()
join s in _context.States
on new { ID_T = t.ID, sub.Time } equals new { s.ID_T, s.Time }
into sj
from s in sj.DefaultIfEmpty()
select new { t.ID, t.Description, State = (s == null ? false : s.State) };
In LINQ it's not as elegant as my original SQL query but semantically it's the same and it does more or less the same thing on the DB side.
In EF6 it is also much more convenient to use arbitrary raw SQL queries and AFAIK also the database views.
The biggest downside of this approach is that full .NET framework has to be targeted, EF6 is not compatible with .NET Core.

Update with subselect and where

The following code works fine in T-SQL, but not in JET SQL, in Access:
UPDATE Superliste_Temp
SET [Plan-TGrp-Spanne_Stfl1] =
(SELECT [Plan-TGrp-Spanne_Stfl1]
FROM Superliste_Temp
INNER JOIN dbo_Common_preferences
ON Superliste_Temp.Teil = dbo_Common_preferences.AktivesTeil)
WHERE [Teilegruppe] =
(SELECT [Teilegruppe]
FROM Superliste_Temp
INNER JOIN dbo_Common_preferences
ON Superliste_Temp.Teil = dbo_Common_preferences.AktivesTeil);
Why does it not work!?
I have a hard time looking at that SQL to figure out what it's trying to do, but I know that Jet SQL needs the SET statement after the JOINS, so my best guess is:
UPDATE Superliste_Temp, Superliste_Temp AS ST1
INNER JOIN dbo_Common_preferences AS pref1
ON ST1.Teil = pref1.AktivesTeil
SET Superliste_Temp.[Plan-TGrp-Spanne_Stfl1] = [ST1]![Plan-TGrp-Spanne_Stfl1]
WHERE (((Superliste_Temp.Teilegruppe)=[ST1]![Teilegruppe]));

Count the number of elements in sql server table

I have 2 tables which looks like this:
ARTICLES TABLE:
and the output should look like this:
How can I accomplish this using both sql query (i'm using sql server 2005) and using linq to sql query ?
BTW i'm using sql server 2005, asp.net with c# in Visual studio 2008.
Please help me
Thanks in anticipation
Update: Added Linq experssion that can be used if you require an OUTER join.
INNER JOIN
For an inner join ie. only get back the articles that have been bought at least once, you can use the following.
LINQ 2 SQL
from a in Articles
join c in CustomersRecords on
a.Article_Name equals c.Article_Name
group a by new {a.SNo, a.Article_Name} into g
select new
{
SNo = g.Key.SNo,
Article_Name = g.Key.Article_Name,
Total_Items_Bought = g.Count()
}
The above translates to the following SQL
SELECT COUNT(*) AS [Total_Items_Bought], [t0].[SNo], [t0].[Article_Name]
FROM [Articles] AS [t0]
INNER JOIN [CustomersRecord] AS [t1] ON [t0].[Article_Name] = [t1].[Article_Name]
GROUP BY [t0].[SNo], [t0].[Article_Name]
Which when cleaned-up a little gives you
SELECT a.SNo,
a.Article_Name,
COUNT(*) AS Total_Items_Bought
FROM Articles AS a
INNER JOIN CustomersRecord AS c ON a.Article_Name = c.Article_Name
GROUP BY a.SNo, a.Article_Name
LEFT OUTER JOIN
For a left outer join ie. get back all articles event those that have never been bought, you can use the following.
LINQ 2 SQL
from a in Articles
join c in CustomersRecords on
a.Article_Name equals c.Article_Name into apc
select new
{
SNo = a.SNo,
Article_Name = a.Article_Name,
Total_Items_Bought = apc.Count()
}
This translates to the following SQL
SELECT [t0].[SNo], [t0].[Article_Name], (
SELECT COUNT(*)
FROM [CustomersRecord] AS [t1]
WHERE [t0].[Article_Name] = [t1].[Article_Name]
) AS [Total_Items_Bought]
FROM [Articles] AS [t0]
select
A.SNo,
A.Article_Name,
count(C.Article_Name) as Total_Items_Bought
from Articles as A
left outer join CustomersRecord as C
on A.Article_Name = C.Article_Name
group by A.SNo, A.Article_Name
order by A.SNo
Use this for SQL
SELECT
SNO,Article_Name,
(SELECT COUNT(*) FROM CustomersRecord AS cr
WHERE cr.Article_Name = Article_Name) AS Total_Items_Bought
FROM ARTICLES

NHibernate Collection Left Outer Join Where Clause Issue

It seems that when using the following NHibernate query, I do not get a root entity when the left outer join has no records.
ICriteria critera = session.CreateCriteria(typeof(Entity));
criteria.CreateCriteria("SubTable.Property", "Property", NHibernate.SqlCommand.JoinType.LeftOuterJoin);
criteria.Add(Expression.Not(Expression.Eq("Property", value)));
The SQL that I am trying to generate is:
SELECT * FROM BaseTable
LEFT JOIN (
SELECT * FROM SubTable
WHERE Property <> value
)Sub ON Sub.ForeignKey = BaseTable.PrimaryKey
Notice that the where clause is inside the left join's select statement. That way if there arent any maching sub records, we still get a top level record. It seems like NHibernate is producing the following SQL.
SELECT * FROM BaseTable
LEFT JOIN (
SELECT * FROM SubTable
)Sub ON Sub.ForeignKey = BaseTable.PrimaryKey
WHERE Sub.Property <> value
Is there anyway to achieve that first piece of SQL? I have already tried:
ICriteria critera = session.CreateCriteria(typeof(Entity));
criteria.CreateCriteria("SubTable.Property", "Property", NHibernate.SqlCommand.JoinType.LeftOuterJoin);
criteria.Add(
Restrictions.Disjunction()
.Add(Expression.IsNull("Property"))
.Add(Expression.Not(Expression.Eq("Property", value)));
I am looking for a solution using the Criteria API.
Try this:
var hql = #"select bt
from BaseTable bt
left join bt.SubTable subt
with subt.Property <> :property";
Or perhaps:
var hql = #"select bt
from BaseTable bt
left join bt.SubTable subt
where subt.ForeignKey = bt.PrimaryKey
and subt.Property <> :property";
Finally:
var result = session.CreateQuery(hql)
.SetParameter("property", "whateverValue")
.List<BaseTable>();
I don't use nHibernate but I think this is the SQL you need to generate:
SELECT *
FROM BaseTable
LEFT JOIN SubTable sub
ON Sub.ForeignKey = BaseTable.PrimaryKey and sub.Property <> value
What you want isn;t a where clasue but an additional condition on the join. Hope that helps.

Resources