I\'m trying to upgrade a solution to the new Core Framework 3.0.0. Now I\'m having a small issue I don\'t understand.
Look, this method was unproblematic in 2.2.6:
I had the same issue when i upgraded to efcore 3.0. What has changed in efcore 3.0 is that when he can not execute the query, he will just stop and throw an error.
The behaviour was the same if the previous versions of efcore, but when he did not recognized the parameters, he would just execute the query without the parameters (get more data then u wanted) but maybe you never noticed.
You can check that in the logs.
If you want to execute queries in efcore 3.0 you'll have to use fields in your query that are also in your db tables. He will not recognize object properties that are not mapped to de db
it's a pain right now to re-write queries but it will be a win in a while
As you are trying to upgrade your solution's .netCore version to 3.0, I will answer your question in the scope of a person performing an upgrade:
By referencing the EF Core 3.0 breaking changes official docs, you will find the line
LINQ queries are no longer evaluated on the client
Your query below will no longer be evaluated client side because GetValueOrDefault() cannot be interpreted by EF:
.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
The reason this was working prior to 3.0 was because it evaluates everything prior to the segment where it cannot translate to raw SQL, then evaluate on the client (c#) side the rest of the segments. This means that your code is roughly evaluated to:
return (await ApplicationDbContext.Users
.Where(x => x.Gender != ApplicationUser.GenderTypes.generic).ToListAsync()) //sql evaluated till here
.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
.Where(x => x.RetireDate == null)
.OrderBy(x => x.BirthDate.GetValueOrDefault())
.ToList();
This is no longer allowed in EF Core 3.0 because the rationale was that hiding away client side evaluation is disadvantageous in production with larger datasets whereas in development, performance hits may be overlooked.
You have 2 solutions.
The preferred is to rewrite the affected line to be something like this, with the defaultMonthValue being a const int with some default month integer that was used inside your GetValueOrDefault() extension.
.Where(x => (x.BirthDate != null && x.BirthDate.Value.Month == DateTime.Now.Month) || (x.BirthDate == null && defaultMonthValue == DateTime.Now.Month))
The second, but not recommended solution is to explicitly add .AsEnumerable() before the problem segment here to force EF to evaluate the prior statements.
.AsEnumerable() // switches to LINQ to Objects
.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
Some tips for people who intend to migrate to 3.0 from 2.2 and want to test the client evaluation breaking change in your 2.2 codebase prior to actual migration:
As from Microsoft docs, add the following to your startup.cs to simulate 3.0 client side query throws.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
In my case the issue was by using Ticks from DateTime
x.BirthDate.Ticks
For future readers.
I got this same issue.
By making a simple substitution for an "inline date calculation", I was able to get past the issue.
"BEFORE" IQueryable (below) (does not work):
(just focus on this part in the "before"
"perParentMaxUpdateDate.UpdateDateStamp < DateTime.Now.Add(cutOffTimeSpan)"
)
IQueryable<MyChildEntity> filteredChildren = from chd in this.entityDbContext.MyChilds
join perParentMaxUpdateDate in justParentsAndMaxUpdateTs
on new { CompoundKey = chd.UpdateDateStamp, chd.MyParentUUID } equals new { CompoundKey = perParentMaxUpdateDate.UpdateDateStamp, perParentMaxUpdateDate.MyParentUUID }
where magicValues.Contains(chd.MyChildMagicStatus)
&& perParentMaxUpdateDate.UpdateDateStamp < DateTime.Now.Add(cutOffTimeSpan) /* <- FOCUS HERE */
select chd;
"AFTER" IQueryable (below) (that works fine):
DateTime cutOffDate = DateTime.Now.Add(cutOffTimeSpan); /* do this external of the IQueryable......and use this DateTime ..... instead of the "inline" time-span calcuation as seen above */
IQueryable<MyChildEntity> filteredChildren = from chd in this.entityDbContext.MyChilds
join perParentMaxUpdateDate in justParentsAndMaxUpdateTs
on new { CompoundKey = chd.UpdateDateStamp, chd.MyParentUUID } equals new { CompoundKey = perParentMaxUpdateDate.UpdateDateStamp, perParentMaxUpdateDate.MyParentUUID }
where magicValues.Contains(chd.MyChildMagicStatus)
&& perParentMaxUpdateDate.UpdateDateStamp < cutOffDate /* <- FOCUS HERE */
select chd;
I'm guessing that because of my issue and the issue of the original question.......DateTimes are a little trickier.
So while (yes) it is annoying that previously working code..stops working......understanding the "defaults to NOT running code on the client" is very important for performance.
PS, I'm on 3.1
My package references for completeness.
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
If your list is IQueryable is where, you need to use Tolist ().
list.ToList().Where(p => (DateTime.Now - p.CreateDate).Days <= datetime).AsQueryable();
As Daniel Hilgarth wrote his solution is fine and works. The Addition of Wiktor Zychla seems to work, too. I rewrote the method as follows:
public async Task<IEnumerable<ApplicationUser>> GetBirthdayUsersCurrentMonth()
{
return await ApplicationDbContext.Users
.Where(x => x.Gender != ApplicationUser.GenderTypes.generic)
//.Where(x => x.BirthDate.GetValueOrDefault().Month == DateTime.Now.Month)
.Where(x => x.BirthDate.Value.Month == DateTime.Now.Month)
.Where(x => x.RetireDate == null)
.OrderBy(x => x.BirthDate)
.ToListAsync();
}
So, as it seems in Core 3.0.0 it's not a good idea to use as mentioned evaluation-methods event if these are standard methods served by the classes itself.
Thanks for your help.