Linq Exception: Function can only be invoked from linq to entities - c#

I have a StudentReceipts table which stores ReceiptNo as string(001,002,003,..,099,..).
I want go get the last receiptno details inorder to increment the receiptno for next transaction.
This is what I have tried
var _lastGeneratedRecDetails = _db.StudentReceipts
.AsEnumerable()
.Where(r => r.Status == true
&& EntityFunctions.TruncateTime(r.DueDate.Value) >= _startDate.Date
&& EntityFunctions.TruncateTime(r.DueDate.Value) <= _endDate.Date)
.OrderByDescending(x => Int32.Parse(x.ReceiptNo))
.FirstOrDefault();
But there i am getting the following exception
this function can only be invoked from linq to entities
Any help will be highly appreciated.

By calling .AsEnumerable() you are going from Linq-To-Entities to Linq-To-Object. By calling it, you are also filtering all the results in memory, so you are pulling the whole StudentReceipts table from the database everytime you do that query as it gets executed past the .AsEnumerable() method. The general rule is to try to do as much as you can on the database side:
var _lastGeneratedRecDetails =
_db.StudentReceipts.Where(r => r.Status == true
&& EntityFunctions.TruncateTime(r.DueDate.Value) >= _startDate.Date
&& EntityFunctions.TruncateTime(r.DueDate.Value) <= _endDate.Date)
.AsEnumerable()
.OrderByDescending(x => Int32.Parse(x.ReceiptNo))
.FirstOrDefault();
If you do it like this, you will filter everything in the database and fetch the filtered results. I don't know what type x.ReceiptNo is though, but calling Int.Parse isn't allowed in Linq-To-Entities. You can filter first and then call AsEnumerable to be able to do the parsing and ordering in memory.

In my case, I was re-using a Func / Filter expression that included DbFunctions.TruncateTime in a follow-up processing statement AFTER I had already processed the query in SQL. Removing it cleared the instance of the exception for me.

use and
.AsQueryable()
var _lastGeneratedRecDetails = _db.StudentReceipts
.AsEnumerable().AsQueryable()

Related

How to make a linq-query with multiple Contains()/Any() on possibly empty lists?

I am trying to make a query to a database view based on earlier user-choices. The choices are stored in lists of objects.
What I want to achieve is for a record to be added to the reportViewList if the stated value exists in one of the lists, but if for example the clientList is empty the query should overlook this statement and add all clients in the selected date-range. The user-choices are stored in temporary lists of objects.
The first condition is a time-range, this works fine. I understand why my current solution does not work, but I can not seem to wrap my head around how to fix it. This example works when both a client and a product is chosen. When the lists are empty the reportViewList is obviously also empty.
I have played with the idea of adding all the records in the date-range and then removing the ones that does not fit, but this would be a bad solution and not efficient.
Any help or feedback is much appreciated.
List<ReportView> reportViews = new List<ReportView>();
using(var dbc = new localContext())
{
reportViewList = dbc.ReportViews.AsEnumerable()
.Where(x => x.OrderDateTime >= from && x.OrderDateTime <= to)
.Where(y => clientList.Any(x2 => x2.Id == y.ClientId)
.Where(z => productList.Any(x3 => x3.Id == z.ProductId)).ToList();
}
You should not call AsEnumerable() before you have added eeverything to your query. Calling AsEnumerable() here will cause your complete data to be loaded in memory and then be filtered in your application.
Without AsEnumerable() and before calling calling ToList() (Better call ToListAsync()), you are working with an IQueryable<ReportView>. You can easily compose it and just call ToList() on your final query.
Entity Framework will then examinate your IQueryable<ReportView> and generate an SQL expression out of it.
For your problem, you just need to check if the user has selected any filters and only add them to the query if they are present.
using var dbc = new localContext();
var reportViewQuery = dbc.ReportViews.AsQueryable(); // You could also write IQuryable<ReportView> reportViewQuery = dbc.ReportViews; but I prefer it this way as it is a little more save when you are refactoring.
// Assuming from and to are nullable and are null if the user has not selected them.
if (from.HasValue)
reportViewQuery = reportViewQuery.Where(r => r.OrderDateTime >= from);
if (to.HasValue)
reportViewQuery = reportViewQuery.Where(r => r.OrderDateTime <= to);
if(clientList is not null && clientList.Any())
{
var clientIds = clientList.Select(c => c.Id).ToHashSet();
reportViewQuery = reportViewQuery.Where(r => clientIds.Contains(y.ClientId));
}
if(productList is not null && productList.Any())
{
var productIds = productList.Select(p => p.Id).ToHashSet();
reportViewQuery = reportViewQuery.Where(r => productIds .Contains(r.ProductId));
}
var reportViews = await reportViewQuery.ToListAsync(); // You can also use ToList(), if you absolutely must, but I would not recommend it as it will block your current thread.

How to filter collection in linq based on some subobjects in that list

I want to get only those employees that have at least one service, and which that service is younger than current date (dt)
I tried with .Any() but it returns me all employees with all services (it doesnt check that date)
var employees =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(x => x.IsCounter && x.Services != null && x.Services.Count > 0 &&
x.Services.Any(u => u.ActiveTo >= dt.Value));
I want to filter just those employees which have at least one service or more where ActiveTo is not in the past (dt is a current datetime.now)
You can chain multiple .Where and .Select statements after one another. Your LINQ query is very hard to read without more specific information about your objects.
To make it more readable, I would suggest splitting your requirements into separate queries, like so:
var employeesWithActiveServices =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(e => e.IsCounter && e.Services.Count >= 1)
.Select(e => e.Services.Contains(s => s.ActiveTo >= DateTime.Now)).ToList();
Notice how I removed your e.Services != null check. It is redundant when you're already checking e.Services.Count.
This was made quickly of the top of my head, so you may need to tweak it to suit your needs.
It is still a hard LINQ query to read without seeing the objects it is querying, but this at least makes the query itself more readable.
Try to remove any additional null, Count checks, otherwise SQL will be complex and slow:
var employees =
employeeService.GetAllActiveEmployeesForCompanyForLocation(companyId, location.Id)
.Where(x => x.IsCounter &&
x.Services.Any(u => u.ActiveTo >= dt.Value));
EF Core translates your query into the SQL, which do not have NullReference exception.

LINQ expression could not be translated

I have the following database query where I am trying to check if there exists an item with a particular barcode that is linked to a particular mailbag. The query is as follows:
var exists = await dbcontext.Items
.Include(t => t.MailBagItems)
.ThenInclude(mt => mt.MailBag)
.AnyAsync(t => t.Barcode.Equals(barcode) &&
t.MailBagItems.FirstOrDefault() != null &&
t.MailBagItems.FirstOrDefault().MailBag.Number.ToLower().Equals(mailbagNumber.ToLower()));
For some reason, I'm getting the following exception:
System.InvalidOperationException: The LINQ expression 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().
I know for a fact from removing parts of the boolean expression that the issue is in the last boolean condition where I'm checking the mailbag number. However, I get the same error if I remove the calls to ToLower(). Can someone indicate what is wrong with my expression and how to fix it? Please note I'm using .NET core 3 and SQL Server.
Managed to make the query work by changing it to the following:
var exists = dbcontext.Items
.AnyAsync(t => t.Barcode.Equals(barcode) &&
t.MailBagItems.Any(t => t.MailBag.Number.ToLower().Equals(mailbagNumber.ToLower())));
Seems it wasn't enjoying the .FirstOrDefault().MailBag before.
Your AnyAsync is to complex for EF to transform to SQL, if you want to still use that query you will have to materialize the entities first, like this:
var exists = dbcontext.Items
.Include(t => t.MailBagItems)
.ThenInclude(mt => mt.MailBag)
.ToListAsync()
.AnyAsync(t => t.Barcode.Equals(barcode) &&
t.MailBagItems.FirstOrDefault() != null &&
t.MailBagItems.FirstOrDefault().MailBag.Number.ToLower().Equals(mailbagNumber.ToLower()));
Also you are missing the await keyword, or was that intended?

ASP.NET Entity Framework .Where() with Lesser than

How can I make this lesser than or equal work in my .Where() clause? I am getting an error.
var filteredProducts = Products.Where(p => p.State.Contains("Bruikbaar"))
.Where(p => p.Privilege <= ui.GetPrivilegeNumber())
.ToList();
Error:
LINQ to Entities does not recognize the method 'Int32 GetPrivilegeNumber()' method, and this method cannot be translated into a store expression.
I hope this question is never asked before. Googled couldn't find it either or I am using the wrong words to express my problem.
ui.GetPrivilegeNumber() is not a recognized method.
Use this:
var uiPrivilege = ui.GetPrivilegeNumber();
var filteredProducts = Products.Where(p => p.State.Contains("Bruikbaar"))
.Where(p => p.Privilege <= uiPrivilege)
.ToList();
And as other users mentionted, you can optimize your Where.
EF does not execute method calls which you use in predicates. It stores them as expression (i.e. syntax tree) and then analyzes this tree to build SQL query by translating C# code to SQL code. It cannot translate GetPrivilegeNumber() method call into SQL, because there is no appropriate SQL code for that. So all you need is move this method call out of expression and pass only result of method call instead:
var privilegeNumber = ui.GetPrivilegeNumber();
var filteredProducts = Products.Where(p => p.State.Contains("Bruikbaar"))
.Where(p => p.Privilege <= privilegeNumber)
.ToList();
Now privilegeNumber is just an integer variable which is translated into SQL parameter
SELECT * FROM Products p
WHERE p.State LIKE '%Bruikbaar%' AND p.Privilege <= #privilegeNumber
You need to move ui.GetPrivilegeNumber() outside of the query. You can also merge those Where queries into a single one:
var privilegeNumber = ui.GetPrivilegeNumber();
var filteredProducts = Products.Where(p =>
p.State.Contains("Bruikbaar")
&& p => p.Privilege <= privilegeNumber)
.ToList();
You can use other evaluation method inside LinQ. To simplified the code, you can use it in little old way of writing LinQ.
var uiPrivilege = ui.GetPrivilegeNumber();
var filteredProducts =(from p in Products
where p.State.Contains("Bruikbaar") && p.Privilege <= uiPrivilege
select p).ToList();
The above query generate same output but easy to understood.

Local sequence cannot be used in LINQ to SQL

Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.
I am getting this error from the below linq query:
List<Something> results = new List<Something>(items
.Where(w => selecteditems.Count == 0 || w.ops.Intersect(selecteditems).Count() > 0)
.ToList()
.OrderBy(a => a.FirstNumber)
.OrderBy(b => b.SecondNumber));
Would the intersect be throwing this error?
The query provider doesn't know how to translate w.ops.Intersect(selecteditems) into a SQL query.
If selecteditems was another query from the same query provider then it may be able to translate them, or if the entire operation were being done in Linq-to-Objects, rather than Linq-to-SQL, then it would be fine.
As per the error message, the only operation it knows how to perform on such an object is Contains. You can re-work your query to use that instead:
.Where(w => selecteditems.Count == 0 ||
w.ops.Any(op => selecteditems.Contains(op)))
That [should] work.

Categories