EF Core dynamic filter on boolean column - entity-framework-core

I want to apply a filter on boolean column which I will get it from the frontend as a parameter. It may be true, false or both. I can use the if statement to run the query but I need to add other methods like Take, Skip, OrderBy etc., which I have removed it for brevity. Is there any way to build the dynamic expression for below condition
if(parameter == "true")
_db.Employees.Where(e => e.Status = true)
else if (parameter == "false")
_db.Employees.Where(e => e.Status = false)
else
_db.Employees.ToList() //Return both true and false

LINQ queries are already dynamic. Every LINQ operator returns a new IQueryable<> query. You can construct a query bit by bit based on various conditions this way, eg :
var query=db.Employees.AsQueryable();
if(status=="Active")
{
query=query.Where(e=>e.Active);
}
if(dateAscending)
{
query=query.OrderBy(e=>e.Date);
}
if(page==1)
{
query=query.Take(pageSize);
}
else
{
query=query.Skip(page*pageSize).Take(pageSize);
}
...
var employees=await query.ToListAsync();
The tricky part is handling NULLs. In SQL, NULL means there's no value and any comparison with NULL returns NULL itself, which is translated as false. If Status or Active are nullable, you can't return any rows with NULLs by comparing with false. You'll have to compare with null explicitly. EF Core translates that to IS NULL.
Using a switch expression you can combine the various Status checks with this:
query = (status) switch
{
"true" => query.Where(e=>e.Status),
"false" => query.Where(e=>!e.Status),
"both" => query.Where(e=> e.Status !=null)
_ => query.Where(e=>e.Status == null)
};
This expression assumes that an empty string is meant to match NULLs. both means retrieving TRUE and FALSE but not NULL.
If don't care about NULLs, this can be simplified to :
query = (status) switch
{
"true" => query.Where(e=>e.Status),
"false" => query.Where(e=>!e.Status),
_ => query
};

Related

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]))

Can't create a condition for null values in an array?

I am using linq expression trees to build a query.
My array:
string[] { null, null }
condition I want to implement:
x == null ? null : x.ToLower()
My linq expression looks like this:
{Param_0 => value(System.String[]).Any(Param_1 => (Param_0.FirstName.ToLower() == IIF((Param_1 == null), null, Param_1.ToLower())))}
This is my first attempt and I can't seem to find the correct way to do it
Constant = Expression.Condition(Expression.Equal(Constant, Expression.Constant(null, typeof(string))), Expression.Constant(null, typeof(string)), Expression.Call(Constant, "ToLower", null));
The expected result is to be able to call .ToLower() on elements that are not null
It seems to me that you want an expression that represents a function call with input a string, and output a string.
Expression<Func<string, string>>
How about a Lambda expression?
Expression<Func<string, string>> myExpression = (x) => (x==null) ? null : x.ToLower();
This expression can be used in a Queryable Select statement, like below:
IQueryable<string> myItems = new List<sring>()
{
"Abc",
null,
"DEF",
null,
"gHI",
}
.AsQueryable();
IQueryable<string> myLowerCaseItems = myItems.Select(myExpression);
foreach (string item in myLowerCaseItems)
{
if (item == null)
Console.WriteLine("<null>");
else
Console.WriteLine(item);
}
This yields the following output:
abc
def
ghi

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));

JPQL: How to write dynamic where conditions

I am struggling with JPQL dynamic where condition. I tried searching the syntax for the same but coluldn't find one.
in my case if user is passing the name parameter then the select query should be
select * from user where name = 'sanjay'
if user is not passing name parameter then select query should be
select * from user
Below is my jpql query format which fails when name parameter is not passed.
entity_manager.createQuery("select u from user u where u.name = :name").setParameter("name",params[:name]).getResultList()
How can i update above JPQL query to support both the cases i.e when the name parameter is passed and when the name parameter is not passed ??
This is not possible in JPQL. You even cannot do something like
createQuery("select u from user u where u.name = :name OR :name IS NULL")
It is not possible. That simple. Use two queries or use the Criteria API.
This is the answer I get when I tries to do like you it is working with some modification.
In my case I had the problem that my optional parameter was a List<String> and the solution was the following:
#Query(value = "SELECT *
FROM ...
WHERE (COLUMN_X IN :categories OR COALESCE(:categories, null) IS NULL)"
, nativeQuery = true)
List<Archive> findByCustomCriteria1(#Param("categories") List<String> categories);
This way:
If the parameter has one or more values it is selected by the left side of the OR operator
If the parameter categories is null, meaning that i have to select all values for COLUMN_X, will always return TRUE by the right side of the OR operator
Why COALESCE and why a null value inside of it?
Let's explore the WHERE clause in all conditions:
Case 1: categories = null
(COLUMN_X IN null OR COALESCE(null, null) IS NULL)
The left part of the OR will return false, while the right part of the OR will always return true, in fact COALESCE will return the first non-null value if present and returns null if all arguments are null.
Case 2: categories = ()
(COLUMN_X IN null OR COALESCE(null, null) IS NULL)
JPA will automatically identify an empty list as a null value, hence same result of Case 1.
Case 3: categories = ('ONE_VALUE')
(COLUMN_X IN ('ONE_VALUE') OR COALESCE('ONE_VALUE', null) IS NULL)
The left part of the OR will return true only for those values for which COLUMN_X = 'ONE_VALUE' while the right part of the OR will never return true, because it is equals to 'ONE_VALUE' IS NULL (that is false).
Why the null as second parameter? Well, that's because COALESCE needs at least two parameters.
Case 4: categories = ('ONE_VALUE', 'TWO_VALUE')
(COLUMN_X IN ('ONE_VALUE', 'TWO_VALUE') OR COALESCE('ONE_VALUE', 'TWO_VALUE', null) IS NULL)
As in Case 3, the left part of the OR operator will select only the rows for which COLUMN_X is equale to 'ONE_VALUE' or 'TWO_VALUE'.

How to do a search with EF CodeFirst

Currently, to do a search using EF CodeFirst and a repository pattern, based on user input to multiple text boxes on an mvc search view/page, I do something like the following:
public PagedList<Entity1> PlayerUserSearch(Entity1SearchParameters searchParameters, int? pageSize, int? startEntity, Func<Entity1, object> sortOrder, bool sortDesc)
{
IQueryable<Entity1> query = from entities in this.DataContext.Entity1s.Include("Entity2List")
where entities.Entity2List.Any()
select entities;
if (searchParameters.Entity2PrimaryKeyId.HasValue)
query = query.Where(e => e.Id == searchParameters.Entity2PrimaryKeyId.Value);
if (searchParameters.HasStats.HasValue)
{
if (searchParameters.HasStats.Value)
query = query.Where(u => u.Entity2List.Any(e => e.Stat != null));
else
query = query.Where(u => u.Entity2List.Any(e => e.Stat == null));
}
if (searchParameters.Entity2OtherField.HasValue)
query = query.Where(u => u.Entity2List.Any(e => e.Event.Entity2OtherField == searchParameters.Entity2OtherField));
if (searchParameters.Entity2OtherField2.HasValue)
query = query.Where(u => u.Entity2List.Any(e => e.Event.Entity2OtherField2 == searchParameters.Entity2OtherField2));
if (searchParameters.Active.HasValue)
query = query.Where(e => e.Active == searchParameters.Active.Value);
return this.GetPageByStartEntity(pageSize.Value, startEntity.Value, query, sortOrder, sortDesc);
}
The problem with this is that for every time I add on another where that checks the child of Entity1 (Entity2) for a certain field, it takes on a new " AND EXISTS" clause to the sql statement generated, so that it is doing an exists and checking table Entity2 all over again for every different field checked, rather than doing a single EXISTS on Entity in the query, and checking all fields I tacked on to the query (i.e. EntityOtherField1 and EntityOtherField2). I haven't been able to find a better way to do a search based on user inputs than constantly checking for the input being there (add to the search parameters)) and then tacking on a new where to the current query. Can anyone tell me if there is a better way to do this? Thanks!
I think what you're looking for is using Specification Pattern.
var spec = new specification<Entity2>(s => true);
if (searchParameters.HasStats.Value)
{
spec = spec.And(e => e.Stat != null);
}
if (searchParameters.Entity2OtherField2.HasValue)
{
spec = spec.And(e => e.Event.Entity2OtherField2 == searchParameters.Entity2OtherField2);
}
query = query.Where(u => u.Entity2List.Any(spec));
Or I believe you can make it more standard by separating the filter logic and using spec.IsStatisfiedBy method.
A good framework for repository/specification implemetation on Entity Framework can be found here.