I have a problem querying PostgreSQL database using EF Core 3.1.
The query is very simple
var gamesQuery = this.dbContext.Games.Where(game => game.StartTime > DateTime.Now).AsQueryable();
// 'request.TimeFrom' is of type System.TimeSpan and the value is populated
gamesQuery = gamesQuery.Where(game => game.StartTime.TimeOfDay >= request.TimeFrom);
// .ToList()-int here causes the exception.
var games = gamesQuery.ToList();
The exception message clearly states that the query can not be translated:
"The LINQ expression 'DbSet\r\n .Where(g => g.StartTime > DateTime.Now)\r\n .Where(g => g.StartTime.TimeOfDay >= __request_TimeFrom_0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information."
The problem is that the same query works fine in .NET Core 2.2.
I haven't found anything about the problem yet.
Someone know what is the reason about this one or am I missing something?
Currently PostgreSQL EF Core 3.x query provider does not support translation of DateTime.TimeOfDay - see the TODO comment in the source code.
Most likely it "worked" in 2.x by silently using client evaluation. But implicit client evaluation has been removed in 3.0 and there is no way to turn it back on.
You can try the following equivalent construct:
.Where(game => (game.StartTime - game.StartTime.Date) >= request.TimeFrom)
At least it doesn't produce the aforementioned exception.
If it doesn't work, take their advice and switch explicitly to client evaluation by inserting AsEnumerable() in the appropriate place before the non translatable expression.
I haven't tried this yet but one solution could be to save TimeOfDay into database beside of DateTime property. Then you just compare it with your TimeSpan variable.
Related
I want to check if a list contains any item from another list using EF Core with Npsql provider. Then I want to get the exact item that was matched in my Dto.
My code is the following (note: Reports.Models is List<string> and so is request.Models as well. The request is consumer filter/search):
var x = await _dbContext.Reports
.Where(x => x.Models.Any(i => request.Models.Contains(i)))
.Select(x => new ReportDto
{
// Model = x.Identifiers.First(i => request.Identifiers.Contains(i)) // this also fails.
Model = request.Models.First(i => request.Models.Any(y => y == i)), // fails on this line
})
.ToListAsync(cancellationToken);
I tried both ways using Any and Contains, neither work. They both return the same error which says:
System.InvalidOperationException: The LINQ expression 'i => __request_Models_0 .Contains(i)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
But I don't understand why? The error never changes, even in my case when I use Any, it still complains about Contains.
You are effectively trying to push your request.Models to the database Server for it to evaluate if any of its datasets are in it. That won't work.
You need to request the Models from the database first and compare them locally OR transform your request.Models into a set of IDs that the database can compare against.
I am facing a problem executing the below query in .NET 6.
query = context.Where(user =>
user.Email.Contains(model.Email,
StringComparison.InvariantCultureIgnoreCase));
After searching the web I understood that EF Core does translate Contains for server-side evaluation - but not the overload that accepts StringComparison.InvariantCultureIgnoreCase or any other StringComparison. But never found the correct way to solve this issue
So I changed the query to something as follows to make it work:
query = context.Where(user =>
user.Email.ToLower().Contains(model.Email.ToLower());
Even though it is working I am not entirely happy with this solution and still wondering which solution solves my problem best. Would using ToLowerInvariant() be a better solution? Any better approach to solve this?
UPDATE
ToLowerInvariant() does not work and causes the same error caused by StringComparison.InvariantCultureIgnoreCase
It seems like your are writing your LINQ query on a DbSet. This is not possible as it cannot be translated to SQL statements.
You could however use the EF.Functions.Like function. This gets translated to the SQL provider and is by default case insensitive.
query = context.Where(user =>
EF.Functions.Like(user.Email, model.Email));
How your query reacts depends on the collation you set on the server side. After all your linq expressions will be translated into an SQL query and how that is interpreted will depend on your database and column settings.
What you could try is stating a collation in your query e.g.
var customers = context.Customers
.Where(c => EF.Functions.Collate(c.Name, "latin1_general_ci collation") == "John")
.ToList();
//SQL_Latin1_General_CP1_CI_AS for SQL Server
//latin1_general_ci collation for MySQL
as found in the Microsoft documentation. Where CI stands for case-insensitive (opposed to CS). Be aware that this query won't be able to leverage the index on the Name due to the custom collation. So it would be better to define it on the column (or table/database).
Try this:
query = context.Where(user => EF.Functions.Collate(user.email,
"SQL_Latin1_General_CP1_CI_AI").Contains(model.Email));
I am currently trying to convert an existing SQL server query to EF Core. The goal is to get all users and get their latest order date-time and latest support request date-time. I want to ensure users are returned even if they don't have an order yet or a support request yet. If they have not placed an order, the column for "latest order date-time" should be NULL. If they have not filed a support request, the column for "latest support request date-time" should be NULL.
The outputted columns should be: Id, Name, Email, LatestOrderDateTime, LatestSupportRequestDateTime
Here is my working SQL server query:
SELECT [User].[Id], [User].[Name], [User].[Email], MAX([Order].[DateTime]) as LatestOrderDateTime, MAX([SupportRequest].[DateTime]) as LatestSupportRequestDateTime FROM [User]
LEFT JOIN [Order] on [User].[Id] = [Order].[UserId]
LEFT JOIN [SupportRequest] on [User].[Id] = [SupportRequest].[ConsumerId]
GROUP BY [User].[Id], [User].[Name], [User].[Email]
ORDER BY [User].[Id]
This is what I've tried, however it does not evaluate on the server:
await this.context.User
.GroupBy(u => new { u.Id, u.Name, u.Email })
.Select(g => new
{
id = g.Key.Id,
name = g.Key.Name,
email = g.Key.Email,
lastOrderDateTime = g.Max(o => o.Orders.Select(o => o.DateTime)),
lastSupportRequestDateTime = g.Max(o => o.SupportRequests.Select(s => s.DateTime)),
})
.OrderBy(c => c.id)
.ToListAsync();
I just want to convert this query to EF core (where the query DOES NOT get evaluated locally).
If you could do it in method syntax, that'd be great, but no worries if not since I can convert it with JetBrains Rider.
Thank you so much for your help!
I just want to convert this query to EF core (where the query DOES NOT get evaluated
locally).
Can not be done, use EntityFramework 6.4, not core, if you want this.
The SQL generation in current EntityFramework (and I mean current up to the nightly builds of veryion 5) is EXTREMELY Limited in the SQL it can generate, combined with what looks like utter ignorance to even accept that fact from the team (which reminds me of the times of EntityFramework 2 and 3 until that team started being serious about LINQ in their version 4).
If it tells you it can not generate this as SQL then your only 2 choises are:
Use EntityFramework 6.4 (which works in dotnetcore 3.1) and get server side execution
Open a bug report, HOPE someone picks it up and then either wait until November for the release of version 5 OR - once it possibly is fixed - work with nightly builds until then.
This is not a syntax issue. They deactivated client evaluation of SQL and their SQL generator is not able to handle a LOT of standard cases. Given you do not want the first (which is what we do at the moment), their feature set just means it can not be done.
You could try to explicitly spell out the left joins in Linq (left join syntax is a bit un-intuitive iirc so it may take some doing to sort it out).
You can find more information at:
https://learn.microsoft.com/en-us/dotnet/csharp/linq/perform-left-outer-joins
The way your Linq is set up specifically asks for object linking, which is why it happens client side. I believe what you're trying to do has a solution in EF Core.
This is my code:
var icdCodes = DbContext.MasterIcd.Select(x => x.IcdCode).AsQueryable();
var count = icdCodes.Where(x => !x.Any(char.IsDigit)).Count();
I'm trying to find those IcdCodes which doesn't contain any characters. But using count throws the following error: “Internal .NET Framework Data Provider error 1025.”
As mentioned in Internal .NET Framework Data Provider error 1025 I'm using AsQuerable() but still getting the error. Please help
The AsQueryable() does not solve this other cause of the same error. As also explained in Casting LINQ expression throws "Internal .NET Framework Data Provider error 1025.", the problem is that Where(x => !x.Any(char.IsDigit)) can't be translated to SQL.
The C# code you use treats a string as a char array and calls a function that uses a Unicode lookup table to check if each character is a digit.
The T-SQL variant of this is ISNUMERIC. See How to know if a field is numeric in Linq To SQL:
DbContext.MasterIcd
.Select(x => x.IcdCode)
.Where(i => SqlFunctions.IsNumeric(i) == 1)
.ToList();
Not every Request is translatable into SQL. Splitting a string into an character array and doing array-options on it, might be one of them.
Approach A.
You load all you strings im Memory (replacing AsQueryable with ToList())
And do your Test locally. (your code is OK).
Approach B.
If your IcdCode is of limited length (maximum 9), it could be simpler, just to TryParse it to int or long, disallowing signs if you want. With this Type-Conversion approach there could be also a solution that can be expressed in SQL. (Like if conversion is possible than ...). But it sometimes hard to find out how to express something in C#, that can be converted to SQL, this is what actually is done when executing LINQ to SQL, and it still depends on the type of SQL-Server itself.
Does anyone know what is the replacement for SqlMethods.DateDiffMonth in the Entity Framework.
This is the error I am getting when I tried to use it.
LINQ to Entities does not recognize the method 'Int32 DateDiffMonth(System.DateTime, System.DateTime)' method, and this method cannot be translated into a store expression.
It would appear that, currently, you need to express this using an eSQL statement that effectively calls down to the SQLServer DATEDIFF method
For example:
db.entity.Where("SqlServer.DATEDIFF('MONTH', [start date], [end date]) = 1");
(See here for further info)
However, when the .NET Framework Version 4.0 is released (or you can use it now if you like playing with beta software! :) you should be able to use the DateDiff method of the new SqlFunctions class, which is part of the new System.Data.Objects.SqlClient Namespace in .NET 4.0
Since you are using LINQ to Entities the best way would be to use the following:
var query = query.Where(e => DateTime.Today >= EntityFunctions.TruncateTime(e.StartDateTime)
&& DateTime.Today <= EntityFunctions.TruncateTime(e.EndDateTime));