ANY with ALL in Entity Framework evaluates locally - c#

I have the following Entity Framework 2.0 query:
var user = context.Users.AsNoTracking()
.Include(x => x.UserSkills).ThenInclude(x => x.Skill)
.Include(x => x.UserSkills).ThenInclude(x => x.SkillLevel)
.FirstOrDefault(x => x.Id == userId);
var userSkills = user.UserSkills.Select(z => new {
SkillId = z.SkillId,
SkillLevelId = z.SkillLevelId
}).ToList()
Then I tried the following query:
var lessons = _context.Lessons.AsNoTracking()
.Where(x => x.LessonSkills.All(y =>
userSkills.Any(z => y.SkillId == z.SkillId && y.SkillLevelId <= z.SkillLevelId)))
.ToList();
This query evaluates locally and I get the message:
The LINQ expression 'where (([y].SkillId == [z].SkillId) AndAlso ([y].SkillLevelId <= [z].SkillLevelId))' could not be translated and will be evaluated locally.'.
I tried to solve it using userSkills instead of user.UserSkills but no luck.
Is there a way to run this query on the server?

You should try limiting the usage of in-memory collections inside LINQ to Entities queries to basically Contains on primitive value collection, which currently is the only server translatable construct.
Since Contains is not applicable here, you should not use the memory collection, but the corresponding server side subquery:
var userSkills = context.UserSkills
.Where(x => x.UserId == userId);
var lessons = context.Lessons.AsNoTracking()
.Where(x => x.LessonSkills.All(y =>
userSkills.Any(z => y.SkillId == z.SkillId && y.SkillLevelId <= z.SkillLevelId)))
.ToList();
or even embed the first subquery into the main query:
var lessons = context.Lessons.AsNoTracking()
.Where(x => x.LessonSkills.All(y =>
context.UserSkills.Any(z => z.UserId == userId && y.SkillId == z.SkillId && y.SkillLevelId <= z.SkillLevelId)))
.ToList();

Use Contains on the server then filter further on the client:
var userSkillIds = userSkills.Select(s => s.SkillId).ToList();
var lessons = _context.Lessons.AsNoTracking()
.Where(lsn => lsn.LessonSkills.All(lsnskill => userSkillIds.Contains(lsnskill.SkillId)))
.AsEnumerable() // depending on EF Core translation, may not be needed
.Where(lsn => lsn.LessonSkills.All(lsnskill => userSkills.Any(uskill => uskill.SkillId == lsnskill.SkillId && lsnskill.SkillLevelId <= uskill.SkillLevelId)))
.ToList();

Related

Is there a way to simplify these linq statements using an .Include()?

Currently I am doing a keyword search on the Plates table (Name column) but also have a Search (searching on SearchTerm column) table which contains Plat Id's that I also want to search and return the corresponding platforms.
The code below works but I'd like to simplify the logic using an .Include statement if possible although I'm not quite sure how. Any help would be greatly appreciated.
if (!string.IsNullOrEmpty(request.Keyword))
{
var searchTermPlateIds = await _db.Search
.Where(x=> x.SearchTerm.ToLower().Contains(request.Keyword.Trim().ToLower()))
.Select(x => x.PlatformId)
.ToListAsync(ct);
var plateFromPlateIds = await _db.Plate
.OrderBy(x => x.Name)
.Where(x => searchTermPlateIds.Contains(x.Id) && x.Status != PlateStatus.Disabled)
.ToListAsync(ct);
plates = await _db.Plates
.OrderBy(x => x.Name)
.Where(x => !string.IsNullOrEmpty(request.Keyword.Trim()) && x.Name.ToLower().Contains(request.Keyword.Trim().ToLower()) && x.Status != PlateStatus.Disabled)
.ToListAsync(ct);
plates = plates.Union(platesFromPlateIds).ToList();
}
Remember simple thing, Include ONLY for loading related data, not for filtering.
What we can do here - optimize query, to make only one request to database, instead of three.
var query = _db.Plates
.Where(x => x.Status != PlateStatus.Disabled);
if (!string.IsNullOrEmpty(request.Keyword))
{
// do not materialize Ids
var searchTermPlateIds = _db.Search
.Where(x => x.SearchTerm.ToLower().Contains(request.Keyword.Trim().ToLower()))
.Select(x => x.PlatformId);
// queryable will be combined into one query
query = query
.Where(x => searchTermPlateIds.Contains(x.Id);
}
// final materialization, here you can add Includes if needed.
var plates = await query
.OrderBy(x => x.Name)
.ToListAsync(ct);

Convert SQL Query to LINQ Lambda C#

I have to fix a query which was already written in the LINQ Lambda, I found the fix in a Simple SQL Query but now I have some trouble in converting it to LINQ Query,
Here is my SQL Query
select * from RequestItem_SubRequestItem x
where x.RequestItem_key = 1 and x.SubRequestItem_key in (
select o.SubRequestItem_key
from SubRequestItem_Entitlement o
inner join SubRequestItem sr on sr.SubRequestItem_key = o.SubRequestItem_key
where o.Entitlement_key = 2 and sr.Action = 'Add' )
And below is my LINQ C# code where I am trying to insert my fixes which include inner join.
z.Entitlements = ARMContext.Context.SubRequestItem_Entitlement
.Where(o => o.Entitlement_key == z.AccessKey && !o.Role_key.HasValue && o.Entitlement.EntitlementConfiguration.UserVisible == true
&& (ARMContext.Context.RequestItem_SubRequestItem
.Where(x => x.RequestItem_key == requestItemKey)
.Select(y => y.SubRequestItem_key)
.Contains(o.SubRequestItem_key)))
.Join(ARMContext.Context.SubRequestItems, subrq => subrq.SubRequestItem_key, temp => requestItemKey, (subrq, temp) => subrq == temp)
Where as previously the C# LINQ code looked like this
z.Entitlements = ARMContext.Context.SubRequestItem_Entitlement
.Where(o => o.Entitlement_key == z.AccessKey && !o.Role_key.HasValue && o.Entitlement.EntitlementConfiguration.UserVisible == true
&& (ARMContext.Context.RequestItem_SubRequestItem
.Where(x => x.RequestItem_key == requestItemKey)
.Select(y => y.SubRequestItem_key)
.Contains(o.SubRequestItem_key)))
When I try to insert the JOIN in the LINQ as per my conditions then I get to see this error.
What is my mistake? Can anybody tell me a correct way to do it?
I think this should Suffice your need, although you might have to make changes to the other code which are dependent on your SubRequestItem_Entitlement table with {user, add}
please have a look at that. As I am sure you will have to make those changes.
.Join(ARMContext.Context.SubRequestItems, user => user.SubRequestItem_key, subreqItems => subreqItems.SubRequestItem_key, (user, subreqItems) => new { user, subreqItems })
.Where(Action => Action.subreqItems.Action == z.ApprovalAction)
you can use this query. I exactly matched the SQL query
var query = ARMContext.Context.RequestItem_SubRequestItem
.Where(a => a.RequestItem_key == 1 && a.RequestItem_key == (ARMContext.Context.SubRequestItem_Entitlement
.Join(ARMContext.Context.SubRequestItems,
right => right.SubRequestItem_key,
left => left.SubRequestItem_key,
(right, left) => new
{
right = right,
left = left
})
.Where(x => x.right.Entitlement_key == 2 && x.left.Action == "Add" && x.right.SubRequestItem_key == a.RequestItem_key).Select(y => y.right.SubRequestItem_key)).FirstOrDefault());

LINQ OrderBy for complex entity

I have complex query:
var containers = this.Repository.Containers
.Include(x => x.PostsContainers)
.ThenInclude(x => x.Post)
.ThenInclude(x => x.TasksPosts)
.ThenInclude(x => x.Task)
.ThenInclude(x => x.AssignedToUser);
var items = containers.Where(x => x.PostsContainers
.Any(y => y.Post.TasksPosts
.Any(z => z.Task.DateDue <= DateTime.UtcNow.AddDays(7)
&& !z.Task.Completed
&& z.Task.AssignedToUserId.Value == userId)));
I got items but I also need to sort these items by Task.DueDate and extract AssignedToUser name.
What's the best way to do it (with good performance, without code duplication)? Maybe I need to rewrite my code?
Try to refactor it this way:
var dateDue = DateTime.UtcNow.AddDays(7)
var result = (from c in this.Repository.Containers
from pc in c.PostsContainers
from tp in pc.Post.TasksPosts
where tp.Task.DateDue <= dateDue
&& !tp.Task.Completed
&& tp.Task.AssignedToUserId == userId
orderby tp.Task.DueDate
select new
{
Container = c,
tp.Task.AssignedToUser
})
.ToList();

The Include path expression must refer to a navigation property defined on the type.

My linq query
model.Questions = db.Questions
.Where (x => x.CategoriesID == categoryId)
.Include (qc => qc.QuestionCounters.Where(x => x.MemberID == User.Identity.GetUserId()))
.Include (qf => qf.QuestionFavorites.Where(x => x.MemberId == User.Identity.GetUserId()))
.Include (qt => qt.QuestionTags)
.ToList();
produces the error
'The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties.'
Any ideas why is this happening?
As some people commented, you cannot use Where method in Include.
Disclaimer: I'm the owner of the project Entity Framework Plus
EF+ Query IncludeFilter feature allow filtering related entities.
model.Questions = db.Questions
.Where (x => x.CategoriesID == categoryId)
.IncludeFiler (qc => qc.QuestionCounters.Where(x => x.MemberID == User.Identity.GetUserId()))
.IncludeFiler (qf => qf.QuestionFavorites.Where(x => x.MemberId == User.Identity.GetUserId()))
.IncludeFiler (qt => qt.QuestionTags)
.ToList();
Wiki: EF+ Query IncludeFilter
Solution #2
Another technique is by using projection (which is what my library do under the hood)
bd.Questions
.Select(q = new {
Question = q,
QuestionCounters = q.QuestionCounters.Where(x => x.MemberID == memberId),
QuestionFavorites = q.QuestionFavorites.Where(x => x.MemberId == memberId),
QuestionTags = q.QuestionTags
})
.ToList()
.Select(x => x.Question)
.ToList();
Ok. Ended up with
IQueryable<HomeViewModel> test = db.Questions
.Where(x => x.CategoriesID == categoryId)
.Select(q => q.ToHomeViewModel(User.Identity.GetUserId()));
and
public static HomeViewModel ToHomeViewModel(this Question q, string memberId)
{
return new HomeViewModel()
{
QuestionCounters = q.QuestionCounters.Where(x => x.MemberID == memberId),
QuestionFavorites = q.QuestionFavorites.Where(x => x.MemberId == memberId),
QuestionTags = q.QuestionTags
};
}
How needs include after all? ;)
Thanks for commenting #jle

Using OR condition in LINQ C#

I do write the following SQL query in LINQ c#
SELECT max(creation_date) from TRS where approval_status='APPROVED' and transaction_type in ('Sale','PRE')
I tried building below query on a list as follows
var session = txns.Where(a => a.transaction_type.Equals("SALE"))
.Where(a => a.transaction_type.Equals("PRE"))
.Where(a => a.approval_status.Equals("APPROVED"))
.OrderByDescending(a => a.creation_date).Select(a => a.creation_date).FirstOrDefault();
The above query didnt work as I wasn't sure of how to use Max and OR condition in LINQ c#
May I know a better solution?
var session = txns
.Where(a => a.transaction_type.Equals("SALE") || a.transaction_type.Equals("PRE"))
.Where(a => a.approval_status.Equals("APPROVED"))
.Select(a=>a.creation_date).Max();
or
var txtypes=new[]{"SALE","PRE"};
var session = txns
.Where(a => txtypes.Contains(a.transaction_type))
.Where(a => a.approval_status.Equals("APPROVED"))
.Select(a=>a.creation_date).Max();
or
var session = txns
.Where(a => a.transaction_type.Equals("SALE") || a.transaction_type.Equals("PRE"))
.Where(a => a.approval_status.Equals("APPROVED"))
.Max(a=>a.creation_date);
You can use || operator to combine your two conditions or you can use Contains which would generate a query like SELECT IN (....)
var transcationTypes = new[] {"SALE", "PRE"};
var sessions = txns.Where(a => transcationTypes.Contains(a.transaction_type)
&& a.approval_status == "APPROVED")
.Select(a => a.creation_date)
.Max();
Once you have filtered out the results you can use Max to select the maximum value.

Categories