Use of List of ints in EF query - Either rewrite the query in a form that can be translated, or switch to client evaluation - entity-framework-core

Using Entity Framework Core 3.1 I have the following query:
var ids = new List<Int32> { 1, 2, 3 };
users = context.Users
.Where(x => ids.All(y => x.UserSkills.Any(z => y == z.SkillId)));
var result = await users.ToListAsync();
When I run this query I get the following error:
'The LINQ expression 'DbSet<User>
.Where(u => True && __ids_0
.All(y => DbSet<UserSkill>
.Where(u0 => EF.Property<Nullable<int>>(u, "Id") != null && EF.Property<Nullable<int>>(u, "Id") == EF.Property<Nullable<int>>(u0, "UserId"))
.Any(u0 => y == u0.SkillId)))' 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()
How can I solve this?

Related

EF Core rewrite the query in a form that can be translated after upgrading

This query was working fine before we upgraded:
var appUsers = await _masterDbContext
.Users
.Include(x => x.UserCustomers)
.AsNoTracking()
.Select(AppUserDto.Projection)
.Where(user => !user.IsDeleted && user.UserCustomers.Any(x => x.CustomerId == tenant.Id && x.UserId == user.Id))
.DistinctBy(x => x.Id)
.ToDataSourceResultAsync(request.GridOptions, cancellationToken: cancellationToken);
We're now getting the error:
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'.
It appears that the DistinctBy is the offender but being fairly new to LINQ I can't figure out how to rewrite it so that it works.
Change DistinctBy to Distinct() and move that and the predicate before the Select. I also shifted the AsNoTracking() up:
var appUsers = await _masterDbContext
.Users
.AsNoTracking()
.Include(x => x.UserCustomers)
.Where(user =>
!user.IsDeleted
&& user.UserCustomers
.Any( x => x.CustomerId == tenant.Id ) )
.Distinct()
.Select(AppUserDto.Projection)
.ToDataSourceResultAsync(request.GridOptions, cancellationToken: cancellationToken);
Looks you have upgraded from EF Core 2.x, which loads full filtered set into the memory. DistinctBy is not translatable by EF Core 6.
Try the following solution:
var filtered = _masterDbContext.Users
.Select(AppUserDto.Projection)
.Where(user => !user.IsDeleted && user.UserCustomers.Any(x => x.CustomerId == tenant.Id && x.UserId == user.Id));
var query =
from d in filtered.Select(d => new { d.Id }).Distinct()
from u in filtered.Where(u => u.Id == d.Id).Take(1)
select u;
var appUsers = await query.ToDataSourceResultAsync(request.GridOptions, cancellationToken: cancellationToken);

The LINQ expression could not be translated. Eiither rewrite the query in a form that can be translated

From a SQL table, I'm trying to get the last line of each item.
I'm passign a list of users (list of objectIds) and want to get the last job of each of them.
Here is the function below.
public async Task<List<Job>> GetLastJobs(List<int> objectIds)
{
using ManagerContext context = new ManagerContext(_callContext);
List<Job> jobs = context.Jobs.Where(j => j.ObjectId.HasValue && objectIds.Contains(j.ObjectId.Value)).GroupBy(j => j.ObjectId).Select(j => j.OrderByDescending(p => p.Id).FirstOrDefault()).ToList();
return null;
}
At exexcution time, it returns:
the LINQ expression '(GroupByShaperExpression:
KeySelector: (j.ObjectId),
ElementSelector:(EntityShaperExpression:
EntityType: Job
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False
)
)
.OrderByDescending(p => p.Id)' 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.
I have no idea how, where to start to solve the problem
The basic problem is that SQL has no powerful grouping operator like LINQ's GroupBy. SQL GROUP BY must aggregate all non-grouping columns, and there's no FIRST() aggregate function in most RDBMSs. So you have to write this query with Windowing Functions, which EF Core hasn't gotten around to.
The alternative way to write this query can be translated.
var jobs = db.Jobs.Where(j => j.ObjectId.HasValue && objectIds.Contains(j.ObjectId.Value))
.Where(j => j.Id == db.Jobs.Where(j2 => j2.ObjectId == j.ObjectId).Max(j => j.Id))
.ToList();
Which translates to
SELECT [j].[Id], [j].[ObjectId]
FROM [Jobs] AS [j]
WHERE ([j].[ObjectId] IS NOT NULL AND [j].[ObjectId] IN (1, 2, 3)) AND ([j].[Id] = (
SELECT MAX([j0].[Id])
FROM [Jobs] AS [j0]
WHERE [j0].[ObjectId] = [j].[ObjectId]))

Evaluate EF query with MAX on the server

Using Entity Framework Core 2.2 I have the following query:
var user = await context.Users.AsNoTracking()
.Include(x => x.Lessons).ThenInclude(x => x.LessonLevel)
.FirstOrDefaultAsync(x => x.Id == userId);
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId < user.Lessons.Max(y => y.LessonLevelId));
Thus query evaluates locally and I get the message:
The LINQ expression 'Max()' could not be translated and will be evaluated locally.'
How can I make this query evaluate on the server?
Update
Based on DavigG answer I made it work using:
var maxLessonLevelId = user.Lessons.Max(y => y.LessonLevelId););
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId < maxLessonLevelId);
I know the following evaluates locally but shouldn't evaluate on the server?
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId <
context.Users.AsNoTracking()
.Where(y => y.Id == userId)
.Select(y => y.Lessons.Max(z => z.LessonLevelId))
.FirstOrDefault());
Is it possible to use a child queries that evaluates on the server?
Get the max value as a separate query, for example:
var maxLessonLevelId = user.Lessons.Max(y => y.LessonLevelId);
Then you can can get the lessons like this:
var lessons = context.Lessons.AsNoTracking()
.Where(x => x.LessonLevelId < maxLessonLevelId);

Conditional WHERE clause on an Entity Framework context

objRecord = await _context.Persons
.Where(tbl => tbl.DeletedFlag == false)
.ToListAsync();
This is the EF code I've got which successfully gets all the records from the Person table where DeletedFlag is false.
I want to add another where criteria that if a surname has been passed in, then add the extra where clause
.Where(tbl => tbl.Surname.Contains(theSurname))
I've tried IQueryable and some other options but can't figure out how to do the equivalent of
string theSurname = "";
objRecord = await _context.Persons
.Where(tbl => tbl.DeletedFlag == false)
if ( theSurname != "") {
.Where(tbl => tbl.Surname.Contains(theSurname))
}
.ToListAsync();
which obviously doesn't work as you can't put an if statement in an EF call.
I can add a criteria afterwards that limits objRecord, but I don't want to retrieve all the records, then cut it down, I'd rather only get the records I need.
You can combine conditions in the Where method by just adding tbl.Surname.Contains(theSurname) so your final query will look like below:
objRecord = await _context.Persons
.Where(tbl => tbl.DeletedFlag == false &&
tbl.Surname.Contains(theSurname))
.ToListAsync();
You have to apply logical AND (&&) with the existing condition in Where clause i.e. tbl.Surname.Contains(theSurname);
So your query would be
.Where(tbl => tbl.DeletedFlag == false && tbl.Surname.Contains(theSurname));

Linq to Entities using Lambda Expressions and multiple where conditions

I am trying to select a list of objects at the end of a fairly long chain of joins/selects using Linq to Entities written as Lambda Expressions... Here is what I have currently the following two statements.
var formDefId = _unitOfWork.AsQueryableFor<FormTrack>()
.Where(x => x.FormTrackId == formTrackId)
.Select(x => x.FormDefId).First();
var rules = _unitOfWork.AsQueryableFor<FormTrack>()
.Where(x => x.FormTrackId == formTrackId)
.Select(x => x.FormDef)
.SelectMany(x => x.Events
.Where(y => y.EventTypeId == 7))
.Select(x => x.RuleGroup)
.SelectMany(x => x.Rules)
.SelectMany(x => x.RuleFormXmls
.Where(y => y.FormDefId == formDefId));
What I would like to do, is combine the two queries, and use the FormDefId returned by
.Select(x => x.FormDef)
in the final where clause instead of having to use the formDefId from a separate query.
Is this something that is possible?
Thank you in advance for your help
It is much easier to write this using query syntax.. Each from in query syntax corresponds to a SelectMany in lambda syntax. This allows you to have all the variables in scope.
var rules =
from ft in _unitOfWork.AsQueryableFor<FormTrack>()
from e in ft.FormDef.Events
from r in e.RuleGroup.Rules
from x in r.RuleFormXmls
where ft.FormTrackId == formTrackId
where e.EventTypeId == 7
where x.FormDefId == ft.FormDefId
select x