Get different results with the same query in T-SQL and LINQ (EF Core) - sql

I'm trying to retrieve records from a database in Azure and for the checks I run T-SQL queries directly and then pass them to LINQ against the EF Core context, but I'm running into this problem.
select FechaOrientativa,id, Archivo, Estado, Estudiar, Descripcion
from Concursos
where FechaOrientativa>=CAST( GETDATE() AS Date ) and Estudiar='pt'
order by FechaOrientativa, Archivo, Estado
When I filter for the records with FechaOrientativa greater than or equal to Today, the Estudiar field is equal to 'pt', I get 2,296 records.
Now in Angular, I do http.Get to my Web API where I execute the following:
[HttpGet("sintratar")]
public async Task<ActionResult<IEnumerable<Concurso>>> GetConcursosSinTratar()
{
return await _context.Concursos.Where(c => c.Estudiar == "pt" && c.FechaOrientativa >= DateTime.Now).OrderBy(c => c.FechaOrientativa).ToListAsync();
}
And to my surprise, I receive only 2,151 records and I can't find an explanation.
Any idea, please?
Thanks.

Compare both SQL and EF LINQ queries, the difference is:
From
Query/Code
Result
SQL
CAST( GETDATE() AS Date )
Returns current Date without Time.
EF LINQ
DateTime.Now
Returns current Date with Time.
Hence the queried results are different.
(Example: Only queried records with the date-time field equal/after the query date-time).
From Date and time functions, you are looking for DateTime.Today.
DateTime.Today
CONVERT(date, GETDATE())
return await _context.Concursos
.Where(c => c.Estudiar == "pt" && c.FechaOrientativa >= DateTime.Today)
.OrderBy(c => c.FechaOrientativa)
.ToListAsync();
Or you can use the SQL query in EF Core with .FromSqlRaw().
return await _context.Concursos
.FromSqlRaw(#"select FechaOrientativa,id, Archivo, Estado, Estudiar, Descripcion
from Concursos
where FechaOrientativa>=CAST( GETDATE() AS Date ) and Estudiar='pt'
order by FechaOrientativa, Archivo, Estado")
.ToList();

Related

Different performance from SQL Server query from Management Studio vs EF Core 5

I wrote a simple EF Core query that makes a select on a table using some where clause to filter data: start date and finish date between the actual date and a field (DescrizioneCommessa) containing a value.
var query = _ctx.Commessas
.Where(x => (x.DataInizioCommessa.HasValue && x.DataInizioCommessa <= DateTime.Now) || !x.DataInizioCommessa.HasValue)
.Where(x => (x.DataFineCommessa.HasValue && x.DataFineCommessa >= DateTime.Now) || !x.DataFineCommessa.HasValue)
.Where(x => x.DescrizioneCommessa.Contains(pattern))
.OrderBy(x => x.DescrizioneCommessa);
To get the raw SQL I just execute the statement:
var sql = facis.ToQueryString();
And the resultant query is:
DECLARE #__pattern_0 nvarchar(50) = N'COMUNE';
SELECT *
FROM [Commessa] AS [c]
WHERE (([c].[DataInizioCommessa] IS NOT NULL AND [c].[DataInizioCommessa] <= GETDATE()) OR [c].[DataInizioCommessa] IS NULL)
AND (([c].[DataFineCommessa] IS NOT NULL AND ([c].[DataFineCommessa] >= GETDATE())) OR [c].[DataFineCommessa] IS NULL)
AND ((#__pattern_0 LIKE N'') OR (CHARINDEX(#__pattern_0, [c].[DescrizioneCommessa]) > 0))
ORDER BY [c].[DescrizioneCommessa]
I notice that it takes very long to perform the query comparing to its hand-written version:
SELECT *
FROM Commessa
WHERE (DescrizioneCommessa LIKE '%COMUNE%')
AND (DataInizioCommessa <= GETDATE() OR DataInizioCommessa IS NULL)
AND (DataFineCommessa >= GETDATE() OR DataFineCommessa IS NULL);
EF Query takes even more than one minute to elaborate, while the normal one is immediate.
I verified that the problem is this part of where clause:
AND ((#__pattern_0 LIKE N'') OR (CHARINDEX(#__pattern_0, [c].[DescrizioneCommessa]) > 0))
If I substitute the above line with:
AND (DescrizioneCommessa LIKE '%COMUNE%')
the problem is resolved, the performance is optimal.
Why this line
.Where(x => x.DescrizioneCommessa.Contains(pattern))
creates this issue?
This is documented behaviour in EF.Core as discussed on SO: Entity framework EF.Functions.Like vs string.Contains
As a general proposition, LIKE expressions do NOT work well with query optimisers. In larger datasets this becomes a serious problem, even though they work just fine in much smaller unoptimized sets. The optimisation is heavily dependent on the pattern being matched and if it is a ranged lookup or not. In your case the pattern cannot make use indexes, EF is simply trying to convert it into an expression that might be indexable, in which case after running the expression enough the rest of the database insights engines would advise you to implement an appropriate index.
Read about some other discussions about parsing String.Contains() to SQL in git hub: https://github.com/dotnet/efcore/issues/474
When you explicitly want to use SQL LIKE, EF Core added EF.Functions.Like():
Like(DbFunctions, String, String)
Like-operator in Entity Framework Core 2.0
var query = _ctx.Commessas
.Where(x => (x.DataInizioCommessa.HasValue && x.DataInizioCommessa <= DateTime.Now) || !x.DataInizioCommessa.HasValue)
.Where(x => (x.DataFineCommessa.HasValue && x.DataFineCommessa >= DateTime.Now) || !x.DataFineCommessa.HasValue)
.Where(x => EF.Functions.Like(x.DescrizioneCommessa, pattern)
.OrderBy(x => x.DescrizioneCommessa);

Converting SQL Server query to Entiry Framework

I need to convert a SQL query to Entity Framework
Sort the values ​​of a field {'115-F-G', '10 -H-G ', '98 -T-R'} in ascending order.
SELECT * FROM ReportePedido
Where PedidoId =145
Order By TipoProducto,
CONVERT(INT, SUBSTRING(EnderecoEstoque, 0, CHARINDEX('-',EnderecoEstoque,1)))
Result: '10 -H-G ','98 -T-R','115-F-G'
It is pretty easy to write as a Linq query, ordering part doesn't seem to worth to be done on server side, it could easily be done on client side:
var query = ctx.ReportePedido
.Where(r => r.PedidoId == 145)
.AsEnumerable()
.OrderBy(r => r.TipoProducto),
.ThenBy(r => int.TryParse(r.Split('-')[0], out int i)?i:int.MaxValue)

How to compare the month parts of two dates?

I am trying to query query the current month, here is my query:
$clients = $this->Clients;
$query = $clients->find();
if($this->Auth->user('role') !== 'admin'){
$query->where(['user_id =' => $this->Auth->user('id')]);
$query->where(['MONTH(dob) = ' => 'EXTRACT(month FROM (NOW()))']);
$query->order(['dob' => 'ASC']);
}
It returns 0 records (my field is a date type), however this query in phpmyadmin works:
SELECT * FROM `clients` WHERE MONTH(dob) = EXTRACT(month FROM (NOW()))
What am I doing wrong?
Just look at the actual generated query (check out your DBMS query log, or try DebugKit), it will look different, as the right hand side value in a key => value condition set is subject to parameter-binding/casting/quoting/escaping. In your case it will be treated as a string, so the condition will finally look something like:
WHERE MONTH(dob) = 'EXTRACT(month FROM (NOW()))'
That will of course not match anything.
You could pass the whole SQL snippet as a single array value, or as an expression object, that way it would be inserted into the query as is (do not insert user values that way, that would create an SQL injection vulnerability!), but I'd suggest to use portable function expressions instead.
CakePHP ships with functions expressions for EXTRACT and NOW, so you can simply do something like:
use Cake\Database\Expression\IdentifierExpression;
use Cake\Database\Expression\QueryExpression;
use Cake\ORM\Query;
// ...
$query->where(function (QueryExpression $exp, Query $query) {
return $exp->eq(
$query->func()->extract('MONTH', new IdentifierExpression('dob')),
$query->func()->extract('MONTH', $query->func()->now())
);
});
Looks a bit complicated, but it's worth it, it's cross DBMS portable as well as auto-quoting compatible. The generated SQL will look something like
WHERE EXTRACT(MONTH FROM (dob)) = (EXTRACT(MONTH FROM (NOW())))
See also
Cookbook > Database Access & ORM > Query Builder > Advanced Conditions
Cookbook > Database Access & ORM > Query Builder > Using SQL Functions
API > \Cake\Database\Expression\QueryExpression::eq()
API > \Cake\Database\FunctionsBuilder::extract()
API > \Cake\Database\FunctionsBuilder::now()

How to convert SQL query to Linq with GROUP BY on only date part of a datetime?

Please help me to convert this SQL query to Linq
SELECT convert(varchar, data_acquisto, 101) as data , SUM(quantita)
FROM Acquisto
WHERE data_acquisto >= DATEADD(month,-1,GETDATE())
GROUP BY convert(varchar, data_acquisto, 101)
This is what I've tried:
var a = from acq in ctx.Acquisto
where acq.data_acquisto >= data
group acq.data_acquisto.ToString("dd/MM/yyyy")
by new { acq.data_acquisto, acq.quantita } into x
select new ListaGrafico()
{
data = x.Key.data_acquisto.ToString("dd/MM/yyyy"),
qta = Convert.ToInt32(x.Key.quantita)
};
This is the error I get:
LINQ to Entities not recognizes the method 'System.String ToString(System.String)'
The Grouping doesn't look correct - the Sql is only grouping by the Date part of the DateTime column. A Linq IGrouping will contain all values grouped by each grouping Key, so no need for the anonymous grouping class.
Convert() will be difficult to convert from Linq to Sql, so I've materialized the data into memory after applying the predicate and done the conversion in Linq to Objects.
I'm afraid I've used the Lambda syntax:
var result = ctx.Acquisto
.Where(acq => acq.data_acquisto >= DateTime.Now.AddMonths(-1))
.ToList()
.GroupBy(acq => acq.data_acquisto.Date)
.Select(x => new ListaGrafico
{
data = x.Key.Date.ToString("dd/MM/yyyy"),
qta = x.Sum(acq => acq.quantita)
});
Also, if possible, I would recommend retaining the Date in a DateTime struct in preference to a string - this would mean changing the data property of ListaGrafico from string to DateTime and then you can omit the .ToString("dd/MM/yyyy") in the final Select projection.

nHibernate 3 - QueryOver with DateTime

I'm trying to write a query to select using the DateTime. Year as a where parameter, but I'm receiving this error from nunit:
NHibernate.QueryException : could not resolve property: Register.Year of: Estudantino.Domain.Events
In class Events I've a property named Register as a DateTime type.
public virtual DateTime Registada { get; set; }
This is the method that is returning the error:
using (ISession session = NHibernateHelper.OpenSession())
{
return session.QueryOver<Evento>()
.Where(x => x.Register.Year == year)
.List();
}
The variable year is of type int, that is been passed to the method.
Does anyone have an idea of what i'm doing wrong? My database server is SQL Server 2005 Express.
QueryOver does not resolve things like DateTime.Year.
Use LINQ instead:
return session.Query<Evento>()
.Where(x => x.Register.Year == year)
.ToList();
In QueryOver, you can just say:
dateTimeColumn.YearPart() == 1999
There is also other extension methods: MonthPart(), DayPart(), etc.
You may need to use HQL here instead. Basically NHibernate does not know really what to do with year. I suspect you need to use the RegisterFunction to register a YEAR function.
Please read this article in its entirety to fully understand what it is you are trying to do.
Create a custom dialect in code
Create a function in SQL server called MyYearFunction that returns a year for a date
Then use HQL (not sure if QueryOver can do this) to get the date where dbo.MyYearFunction(:date) = 12" ...
.Where(x => x.Register >= new DateTime(year, 1, 1) && x.Register <
new DateTime(year + 1, 1, 1))