I want to pass a lambda to my .Select() method depending on a condition.
I set my lambda up like this:
Func<Monthly, int?> f = x => x.CLDD;
I then set up my .Select() like this:
IQueryable query =
db.Monthlies
.GroupBy(o => o.Date.Value.Year)
.Select(
o => new {
Year = o.Key,
MaxDate = o.Max(x => x.Date),
Data = o.Sum(f)
}
)
.Where(o => o.Year != currentYear)
.OrderBy(o => o.Year);
The code compiles and runs fine but the query does not send back any results. When I debug and watch query I see it says:
+ base {"Internal .NET Framework Data Provider error 1025."}
System.SystemException {System.InvalidOperationException}
Note if instead I do:
Expression<Func<Monthly, int?>> f = x => x.CLDD;
Then o.Sum(f) errors saying:
Error 1 Instance argument: cannot convert from
'System.Linq.IGrouping<int,MyWeb.Models.Monthly>' to
System.Linq.IQueryable<MyWeb.Models.Monthly>'
Thank you!
You were close, Entity Framework needs an Expression to work not Func, but the Sum extension method that receives an Expression works only with IQueryable.
Now inside Select you are getting an IGrouping from GroupBy which does not implement IQueryable only IEnumerable.
So you just need to cast it to get the right extension method:
Data = o.AsQueryable().Sum(f)
Related
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.
I have an entity framework object called batch, this object has a 1 to many relationship to items.
so 1 batch has many items. and each item has many issues.
I want to filter the for batch items that have a certain issue code (x.code == issueNo).
I have written the following but Im getting this error:
items = batch.Select(b => b.Items
.Where(i => i.ItemOrganisations
.Select(o => o
.Issues.Select(x => x.Code == issueNo))));
Error 1:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<System.Collections.Generic.IEnumerable<bool>>' to 'bool'
Error 2:
Cannot convert lambda expression to delegate type 'System.Func<Ebiquity.Reputation.Neptune.Model.Item,bool>' because some of the return types in the block are not implicitly convertible to the delegate return type
Select extension method needs a lambda expression that returns a boolean, but the inner o.Issues.Select returns an IEnumerable of boolean to the outer Select(o => o which result in the exception you're getting.
Try using Any instead which verifies that at least one element verifies the condition:
items = batch.Select(
b => b.Items.Where(
i => i.ItemOrganisations.Any(
o => o.Issues.Any(x => x.Code == issueNo)
)
)
);
If I understand correctly, you're trying to select through multiple layers of enumerables. In those cases you need SelectMany which flattens out the layers, not Select. LINQ's syntax sugar is made specifically to make SelectMany easier to reason about:
var items = from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item;
The compiler translates that into something like this:
var items = batch.Items
.SelectMany(item => item.ItemOrganizations, (item, org) => new {item, org})
.SelectMany(#t => #t.org.Issues, (#t, issue) => new {#t, issue})
.Where(#t => #t.issue.Code == issueNo)
.Select(#t => #t.#t.item);
You can always wrap this in a Distinct if you need to avoid duplicate items:
var items = (from item in batch.Items
from org in item.ItemOrganizations
from issue in org.Issues
where issue.Code == issueNo
select item).Distinct();
It's hard to tell what you're trying to do based on your code but I think you're looking for something like this;
var issue = batch.Select(b => b.Items).Select(i => i.Issues).Where(x => x.Code == issueNo).Select(x => x).FirstOrDefault();
The above query will return the first issue where the Issues Code property is equal to issueNo. If no such issue exists it will return null.
One problem (the cause of your first error) in your query is that you're using select like it's a where clause at the end of your query. Select is used to project an argument, when you do Select(x => x.Code == issueNo) what you're doing is projecting x.Code to a bool, the value returned by that select is the result of x.Code == issueNo, it seems like you want that condition in a where clause and then you want to return the issue which satisfies it which is what my query is doing.
items = from b in batch.Include("Items")
where b.Items.Any(x=>x.Code==issueNo)
select b;
You're getting lost in lambdas. Your LINQ chains are all embedded in each other, making it harder to reason about. I'd recommend some helper functions here:
static bool HasIssueWithCode(this ItemOrganization org, int issueNo)
{
return org.Issues.Any(issue => issue.Code == issueNo);
}
static bool HasIssueWithCode(this Item items, int issueNo)
{
return items.ItemOrganizations.Any(org => org.HasIssueWithCode(issueNo));
}
Then your answer is simply and obviously
var items = batch.Items.Where(item => item.HasIssueWithCode(issueNo));
If you inline these functions, the result is the exact same as manji's (so give manji credit for the correct answer), but I think it's a bit easier to read.
So I tried to follow this example to have a sub-query in the where clause of this LINQ query.
var innerquery =
from app in context.applications
select new { app.app_id };
IEnumerable<postDatedCheque> _entityList = context.postDatedCheques
.Where(e => innerquery.Contains(e.appSancAdvice.application.app_id));
The objective was to select those records from postDatedCheques that have app_id in applications table.
But I am getting following erros inside the where clause:
Delegate 'System.Func' does not
take 1 arguments
Cannot convert lambda expression to type 'string' because it is not
a delegate type
'System.Linq.IQueryable' does not contain a
definition for 'Contains' and the best extension method overload
'System.Linq.ParallelEnumerable.Contains(System.Linq.ParallelQuery,
TSource)' has some invalid arguments
Instance argument: cannot convert from
'System.Linq.IQueryable' to
'System.Linq.ParallelQuery'
What am I coding incorrect?
I think a simple join would do the job. It will filter out the 'cheques' that have no relative 'app':
var _entitylist =
from cheque in context.postDatedCheques
join app in context.applications on cheque.appSancAdvice.application equals app
select cheque;
Edit:
Solutions using a .Contains(...) will be translated into a SQL IN statement. Which will be very inefficient. Linq join is translated into SQL INNER JOIN which is very efficient if your DB schema is well trimmed (FKs, index)
What about?
IEnumerable<postDatedCheque> _entityList = context.postDatedCheques.Where(
e => context.applications.Any(
x => e.appSancAdvice.application.app_id == x.app_id));
And if you want to use two statements, set the first as an expression function.
Expression<Func<string, bool>> innerQuery =
x => context.applications.Any(y => y.app_id == x);
IEnumerable<postDatedCheque _entityList =
context.postDatedCheques.Where(
x => innerQuery(x.appSancAdvice.application.app_id));
innerquery is a IQueryable of anonymous type that contains an app_id.
The line Contains(e.appSancAdvice.application.app_id) doesn't make sense since e.appSancAdvice.application.app_id and the anonymous type are not the same type.
Simply do:
var _entityList = context.postDatedCheques
.Where(e =>
context.applications
.Select(a => a.app_id)
.Contains(e.appSancAdvice.application.app_id));
Try this instead:
var innerquery =
from app in context.applications
select new { app.app_id };
IEnumerable<postDatedCheque> _entityList = context.postDatedCheques
.Where(e => innerquery.Any(a => a.app_id == e.appSansAdvice.application.app_id));
I have something like this:
Func<Thread, bool> tmpFunc = thread => true;
threads = Threads.Where(tmpFunc).(...)
Now, when I do Threads.Where(thread => true).(...) everything is okay, but using variable in .Where() crashes my application. Why? Am I doing something wrong?
Okay, so here is the code that reproduces the error:
var threads = context.Categories
.Where(c => c.Name == variable)
.Select(c => new
{
threads = c.Threads
.Where(tmpFunc)
.OrderByDescending(t => t.DateCreated)
.Skip(threadsToSkip)
.Take(threadsPerPage)
.Select(t => new
{
t,
CategoryName = t.Category.Name,
AuthorName = t.Author.UserName,
t.Posts.Count,
LastPost = t.Posts
.OrderByDescending(post => post.DateCreated)
.Select(p => new{p.Author.UserName, p.DateCreated})
.FirstOrDefault()
}),
c.Threads.Count
}).Single();
And the error it gives me is internal .net framework data provider error 1025
Try using an Expression instead of the Func directly:
Expression<Func<Thread, bool>> tmpFuncExpr = thread => true;
While Linq2Objects will be happy with the Func, Linq2Sql will not.
My assumption is that there is no translatiin of a custom func to sql and the func is used in the projection. Although I would expect a "no sql translation for ... exists" exception.
To fix this, you could try declare your func as
Expression<Func<Thread, bool>> tmpFunc = thread => true;
threads = Threads.Where(tmpFunc).(...)
When you're using the Entity Framework, any predicate passed to the EF's IQueryable has to be the Expression<Func<>>.
Check this answer stackoverflow.com/questions/11990158...
I have made myself an ExpressionBuilder class that helps me put together expressions that can be used as a predicate when doing Linq to Sql queries. It has worked great. However, I just discovered Expressions can only be used to filter on Tables, and not on EntitySets??Why on earth is this the case?
For example if I have Company and an Employee with a Salary. I could create these two expressions:
Expression<Func<Company, bool>> cp = x => x.Name.StartsWith("Micro");
Expression<Func<Employee, bool>> ep = x => x.Name.StartsWith("John");
I would then expect to be able to do the following, however it only partially works:
var companies = dataContext.Companies
.Where(cp) // Goes fine
.Select(x => new
{
x.Name,
SumOfSalaries = x.Employees
.Where(ep) // Causes compile-time error
.Sum(y => y.Salary),
}
.ToList();
Also, if I do a ep.Compile() it compiles, but then I get an error when running the query.
Why is this the case? Am I missing something? I don't find this logical. Can I fix this somehow? Or do you have a good workaround?
I know that I in this case could just use Where(x => x.Name.StartsWith("John")) instead, but the problem is that the expressions I need are not that trivial. They are longer strings of AndAlsos and OrElses.
If you are going to pass a lambda expression to a LINQ to SQL provider don't create it as an Expression<T> - let the provider do that for you.
The following works for me - note both are compiled:
var companies = dataContext.Companies.Where(cp.Compile())
.Select(x => new
{
x.Name,
SumOfSalaries = x.Employees
.Where( ep.Compile() )
.Sum(y => y.Salary),
}
).ToList();
The expression parser seems to be losing type information in there somewhere after the first where clause when you put in the second. To be honest, I'm not sure why yet.
Edit: To be clear, I do understand that EntitySet doesn't support passing an expression into the where clause. What I don't completely understand is why it fails when you add the Where(ep.Compile()).
My theory is that in compiling the first where (Where(cp.Compile()), Linq2Sql quits parsing the expression - that it can't parse the ep.Compile() against an entityset, and is unable to decide to break up the query into two until you compile the first where clause.
I think you need to rewrite your query. Another way to ask for the details you want, is: "Give me the sum of the salaries for the selected employees in the selected companies, organized by the company name".
So, with the employee salaries in focus, we can write:
//Expression<Func<Employee, bool>> ep = x => x.Name.StartsWith("John");
//Expression<Func<Company, bool>> cp = x => x.Name.StartsWith("Micro");
Expression<Func<Employee, bool>> ep = x => x.Name.StartsWith("John");
Expression<Func<Employee, bool>> cp = x => x.Company.Name.StartsWith("Micro");
var salaryByCompany = dataContext.Employees
.Where(ep)
.Where(cp)
.GroupBy(employee => employee.Company.Name)
.Select(companyEmployees => new
{
Name = companyEmployees.Key,
SumOfSalaries = companyEmployees.Sum(employee => employee.Salary)
});
var companies = salaryByCompany.ToList();