Reputation: 1012
I previously had the following set up:
public static bool BlogIsLive(BlogPost b)
{
return b.Status == (int)ItemStatus.Active && b.PublishDate < DateTime.Now ;
}
/// Database query
var blogs = (from b in db.BlogPost
where BlogIsLive(b) // <--- super useful, used in multiple places
select b
).ToList()
But after updating to EF Core 3.0, it throws the following error
/// 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 understand this is part of the breaking changes in EF Core 3.0
Now I have to write the query manually in all the places where BlogsIsLive()
was before.
var blogs = from b in db.BlogPost
where b.Status == (int)ItemStatus.Active //<--- Annoying repetition of code
&& b.PublishDate < DateTime.Now //<---
select b
This is highly annoying. Is there no way I can write a method that slots into there?
I know EF has DbFunctions which, for example, can ease the process of comparing Date
values, so I see no reason why it would not be possible to write something of my own that does similar involving Int
, string
or bool
.
Something like:
public static DbFunction BlogIsLive(BlogPost b)
{
//Example
return DbFunction(b.Status == (int)ItemStatus.Active && b.PublishDate < DateTime.Now);
}
/// Database query
var blogs = (from b in db.BlogPost
where MyDbFunctions.BlogIsLive(b)
select b
).ToList();
I've tried a few variations on the above, but no luck.
Thanks.
Upvotes: 1
Views: 1237
Reputation: 131722
The original code has a serious bug that would throw in any non-Core version of EF too - it's a local function, it can't be translated to SQL. Where
accepts expressions as arguments, not functions. You don't need that function anyway.
LINQ works with IQueryable and expressions. Each operator takes one IQueryable and returns another. That's how Where
and Select
work already. This means you can create your own function that adds the Where
condition you want :
public static IQueryable<BlogPost> WhereActive(this IQueryable<BlogPost> query)
{
return query.Where(b=>b.Status == (int)ItemStatus.Active && b.PublishDate < DateTime.Now);
}
And use it with any IQueryable<BlogPost>
, eg :
var finalQuery = query.WhereActive();
var posts=finalQuery.ToList();
Another, more cumbersome option is to construct the Expression<Func<>>
call in code, and pass that to Where
- essentially creating the WHERE condition dynamically. In this case it's not needed though.
EF Core 1.0 added a very unfortunate feature (more like a what-were-they-thinking!
kind of feature), client-side evaluation. If something can't be translated, just load everything in memory and try to filter stuff without the benefit of indexing, execution plans, matching algorithms, RAM and CPUs found in a database server.
This may not be noticed if only 100 rows are loaded by only 1 client at a time, it's a perf-killer for any application with even small amounts of data and concurrent users.
In a web application, this translates to more servers to handle the same traffic.
That's why client-side evaluation was removed when EF 1.0 was introduced back in 2008.
Upvotes: 2
Reputation: 11919
Instead of using db.BlogPost
as the base of the query, you can use a DbSet that already has that filter on it.
DbSet<BlogPost> _allBlogs {get;set;}
IQueryable<BlogPost> ActiveBlogs { get => _allBlogs.Where(b=> b.Status == (int)ItemStatus.Active); }
var blogs = from b in db.ActiveBlogs
select b
Upvotes: 1