Using filters in linq expression tree - c#

My idea is that I expose an api controller ​/api​/Events​/{clientId}​/{projectId}/{eventTypeId}
these tree parameters will be acting like filters in querying my db.Events table.
If I pass ClientId, and not pass other two filters it should just do filtering by ClientId.
I tried with this method:
public async Task<IEnumerable<Event>> Handle(GetEventsByClientIdAndProjectIdQuery query, CancellationToken cancellationToken)
{
Expression<Func<Event, int?>> clientId = (s => query.ClientId);
Expression<Func<Event, int?>> projectId = (s => query.ProjectId);
Expression<Func<Event, int?>> eventTypeId = (s => query.EventTypeId);
if (query.ProjectId.HasValue)
{
projectId = (p => p.ProjectId.Equals(query.ClientId)); //error: cannot implicitly convert type bool to int?
}
if (query.EventTypeId.HasValue)
{
eventTypeId = (e => e.EventTypeId == query.EventTypeId.Value);
}
var evts = await _context.Events.Where(clientId) //error: Argument 2: cannot convert from 'System.Linq.Expressions.Expression<System.Func<UNW.Domain.Entities.Event, int?>>' to 'System.Func<UNW.Domain.Entities.Event, bool>'
.Where(projectId)
.Where(eventTypeId)
.ToListAsync();
if (evts == null)
return null;
return evts.AsReadOnly();
}
and my GetEventsByClientIdAndProjectIdQuery model:
public int? ProjectId { get; set; }
public int? ClientId { get; set; }
public int? EventTypeId { get; set; }
What I am missing?

You can made it simpler
public async Task<IEnumerable<Event>> Handle(GetEventsByClientIdAndProjectIdQuery query,
CancellationToken cancellationToken)
{
var dbQuery = _context.Events;
if (query.ProjectId.HasValue)
{
dbQuery = dbQuery.Where(p => p.ProjectId.Equals(query.ClientId));
}
if (query.EventTypeId.HasValue)
{
dbQuery = dbQuery.Where(e => e.EventTypeId == query.EventTypeId.Value);
}
//same goes for projectID which is missing from your question
var evts = await dbQuery.ToListAsync();
//evts will nerver be null, you might want do something for evts.Count==0
//but it is fine to return an empty list
return evts.AsReadOnly();
}

The most concise solution I can think of
public async Task<IEnumerable<Event>> Handle(GetEventsByClientIdAndProjectIdQuery query, CancellationToken cancellationToken)
=> await _context.Events
.Where(evt => evt.ClientId.Equals(query.ClientId))
.Where(evt => query.ProjectId.HasValue ? evt.ProjectId.Equals(query.ProjectId.Value) : true)
.Where(evt => query.EventTypeId.HasValue ? evt.EventTypeId.Equals(query.EventTypeId.Value) : true)
.ToListAsync(cancellationToken)
.AsReadOnly();
If the filter is provided
Then use it
Otherwise do not filter out the element

I like and am a fan of the simplicity of #Magnetron's answer, but to build off of your existing code:
//error: cannot implicitly convert type bool to int?
Issue 1: The signatures for clientId, projectId and eventTypeId are all set up to return a nullable int (Func<Event, int?), but .Equals() returns a boolean value.
Assuming I'm understanding what you want to accomplish, you can try the below updates:
// 1. Change the return values from int? to boolean.
// 2. Go ahead and set your expression to return Events where the ClientId is equal
// to the ClientId passed in with your query parameter
Expression<Func<Event, bool>> clientId =
(s => s.ClientId.Equals(query.ClientId));
// 3. Similar to the above with ClientId, but we also include the guard clauses for the optional parameters (ProjectId and EventTypeId)
Expression<Func<Event, bool>> projectId =
(s => (!query.ProjectId.HasValue || query.ProjectId == 0) || s.ID.Equals(query.ProjectId));
Expression<Func<Event, bool>> eventTypeId =
(s => (!query.EventTypeId.HasValue || query.EventTypeId == 0) || s.ID.Equals(query.EventTypeId));
// (Issue 2 with clientId resolved by the updates made to resolve Issue 1)
var evts = await _context.Events.Where(clientId)
.Where(projectId)
.Where(eventTypeId)
.ToListAsync();
if (evts == null)
return null;
return evts.AsReadOnly();

Related

C# & Entity Framework linq query how to return the mapper even when object is null

I have the following query. When there is no matching record in the database, the method returns null. But I want the response to return object with null properties. How to achieve this?
public async Task<UserResponse> GetUserByEmployeeId(string employeeNumber)
{
var userRecord = await _context.User
.AsNoTracking()
.Include(u => u.Manager)
.Where(x => x.PersonNumber == employeeNumber)
.Select(user => UserReponseMapper.ToUserResponse(user))
.FirstOrDefaultAsync();
// The following returns expected value:
// But, looking for a better solution
/*
if(userRecord == null)
{
userRecord = new UserResponse();
}
*/
return userRecord;
}
public static UserResponse ToUserResponse(User user)
{
return new UserResponse
{
EmployeeNumber = user.PersonNumber,
ManagerNumber = user.Manager?.PersonNumber
};
}
Expected result when no matching record
{
EmployeeNumber: null,
ManagerNumber: null
}
Currently getting result as NULL;
If there are no matching records, ToUserResponse is not called and FirstOrDefaultAsync returns null. You can use the null-coalescing operator to return the alternative:
public async Task<UserResponse> GetUserByEmployeeId(string employeeNumber)
{
var userRecord = (await _context.User
.AsNoTracking()
.Include(u => u.Manager)
.Where(x => x.PersonNumber == employeeNumber)
.Select(user => UserReponseMapper.ToUserResponse(user))
.FirstOrDefaultAsync())
?? new UserResponse() { EmployeeNumber: null, ManagerNumber: null };
// ...
This operator checks the result of the first parameter against null; if the result is not null, it is returned, otherwise the second parameter is returned. It is basically a short form of the if-statement in your sample.
If you are looking to condense the lines.
userRecord = (userRecord == null) ? new UserResponse() : userRecord;
Or if you want to add it to the original you can add the Elvis operator. "??"
var userRecord = (await _context.User
.AsNoTracking()
.Include(u => u.Manager)
.Where(x => x.PersonNumber == employeeNumber)
.Select(user => UserReponseMapper.ToUserResponse(user))
.FirstOrDefaultAsync())
?? new UserResponse();
// ^ Checks for null
You need to change your construct if user is null as well.
return new UserResponse
{
EmployeeNumber = user?.PersonNumber,
ManagerNumber = user?.Manager?.PersonNumber
};
I was wrong in my comment (and deleted it), but it got me thinking; since you might want to add more logic to the "create this new object when the result is null" you could do something like the "pseudocode" below (it compiles, but it's just testcode)
public async Task<ResultObject> GetResultObjectAsync()
{
return ProcessResult(
(await GetResult())
.Where(x => x?.ID == new Guid())
.FirstOrDefault());
}
// this is your DB
async Task<List<ResultObject>> GetResult()
{
return new List<ResultObject>();
}
// this is the null-coalesce replacement
public static ResultObject ProcessResult(ResultObject? value)
{
if (value == null) return new ResultObject(); // or add more info like setting default values
return value;
}
// this is just a simple POCO for testing
public class ResultObject
{
public Guid ID { get; set; }
}

every Parameter object property that is not null, to be added to expression predicate as a condition

I am looking for a way to dynamically build an expression, based upon the method parameters.
This is the code snippet from my service method, where I would like to build a predicate expression.
public async Task<Account> GetCustomerAccountsAsync(Parameters params)
{
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && ... );
...
}
GetWhereAsync is a method from the generic repository that looks like:
public async Task<IEnumerable<TEntity>> GetWhereAsync(Expression<Func<TEntity, bool>> predicate)
{
return await Context.Set<TEntity>().Where(predicate).ToListAsync();
}
And Parameters class:
public class Parameters
{
public string CustomerId { get; set; }
public string AccountId { get; set; }
public string ProductId { get; set; }
public string CurrencyId { get; set; }
}
What I would like to implement, is that every Parameter object property that is not null,
to be added to expression predicate as a condition.
For example, if CustomerId and AccountId have some values, I would like to dynamically build predicate expression
that would have functionality same as the following predicate:
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && a.AccountId == params.AccountId);
Appreciate any help.
You don't need to use Expressions to build something dynamically here. You can do something like this:
_unitOfWork.Accounts.Where(a =>
(params.CustomerId == null || a.CustomerId == params.CustomerId) &&
(params.AccountId == null || a.AccountId == params.AccountId) &&
(params.ProductId == null || a.ProductId == params.ProductId) &&
(params.CurrencyId == null || a.CurrencyId == params.CurrencyId)
);
This is how I've done queries before for a search form with multiple optional search parameters.
I'm currently working a lot with Expressions so I think I can help you.
I just built a custom code for you.
The code accepts that you add Properties to your filtered class (Account) without having to change the filter building code.
The code filters string Properties and use it to create the Predicate.
public Func<Account, bool> GetPredicate(Parameters parameters)
{
var stringProperties = typeof(Parameters)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(string));
var parameterExpression = Expression.Parameter(typeof(Account));
var notNullPropertyNameToValue = new Dictionary<string, string>();
BinaryExpression conditionExpression = null;
foreach (var stringProperty in stringProperties)
{
var propertyValue = (string)stringProperty.GetValue(parameters);
if (propertyValue != null)
{
var propertyAccessExpression = Expression.PropertyOrField(parameterExpression, stringProperty.Name);
var propertyValueExpression = Expression.Constant(propertyValue, typeof(string));
var propertyTestExpression = Expression.Equal(propertyAccessExpression, propertyValueExpression);
if (conditionExpression == null)
{
conditionExpression = propertyTestExpression;
}
else
{
conditionExpression = Expression.And(conditionExpression, propertyTestExpression);
}
}
}
//Just return a function that includes all members if no parameter is defined.
if (conditionExpression == null)
{
return (x) => true;
}
var lambdaExpression = Expression.Lambda<Func<Account, bool>>(conditionExpression, parameterExpression);
return lambdaExpression.Compile();
}
It returns a typed predicate that you can use in Linq for example.
See this example :
void Main()
{
var customers = new List<Account>()
{
new Account()
{
CustomerId = "1",
},
new Account()
{
CustomerId = "2",
}
};
var predicate = GetPredicate(new Parameters() { CustomerId = "1" });
customers.Where(predicate);
}
If any help is needed, feel free to ask !

How to filter by enum from string in LINQ C#

Is there a way how to filter data with an enum property using a string?
This is my function in service layer which takes 2 arguments for paging feature and 3rd argument is for filtering projects by their status.
I want to do something like this projects.Where(x => x.Status == status) but it throws error because I cannot compare enum with string. Is there some workaround for this?
public async Task<ListResult<ProjectDTO>> GetListedProjects(int pageSize, int pageNumber, string status)
{
var projects = await unitOfWork.ProjectRepository.Get();
//i cannot filter like this
projects.Where(x => x.Status == status);
var orderedProjects = projects.OrderBy(x => x.Name);
var projectList = orderedProjects.ToPagedList(pageNumber, pageSize);
var data = projectList.Select(x => ToDTO.ProjectBuild(x)).ToList();
return new ListResult<ProjectDTO> { Data = data, TotalCount = projectList.TotalItemCount };
}
Here is my project model:
public class Project : ManagementBaseClass
{
[Key]
public int Id { get; set; }
public Status Status { get; set; }
public Priority Priority { get; set; }
//etc just deleted more properties to make this cleaner
}
This is my enum which i use for assigning status to projects, tasks etc
public enum Status
{
New = 1,
Active = 2,
OnHold = 3,
Testing = 4,
Finished = 5,
Dropped = 6
}
You can parse the string to the enum equivalent
Obviously this code is a rough draft to give you an idea and set you on the right track, you'd have to do null checks on the Parse to prevent exceptions, or use TryParse.
public async Task<ListResult<ProjectDTO>> GetListedProjects(int pageSize, int pageNumber, string status)
{
var projects = await unitOfWork.ProjectRepository.Get();
//i cannot filter like this
projects.Where(x => x.Status == (Status)Enum.Parse(typeof(Status), status));
var orderedProjects = projects.OrderBy(x => x.Name);
var projectList = orderedProjects.ToPagedList(pageNumber, pageSize);
var data = projectList.Select(x => ToDTO.ProjectBuild(x)).ToList();
return new ListResult<ProjectDTO> { Data = data, TotalCount = projectList.TotalItemCount };
}
You can parse the string value:
private static TEnum? GetEnum<TEnum>(string value) where TEnum : struct
{
TEnum result;
return Enum.TryParse<TEnum>(value, out result) ? (TEnum?)result : null;
}
public async Task<ListResult<ProjectDTO>> GetListedProjects(int pageSize, int pageNumber, string status)
{
var projects = await unitOfWork.ProjectRepository.Get();
//i cannot filter like this
projects.Where(x => x.Status == GetEnum<Status>(status));
var orderedProjects = projects.OrderBy(x => x.Name);
var projectList = orderedProjects.ToPagedList(pageNumber, pageSize);
var data = projectList.Select(x => ToDTO.ProjectBuild(x)).ToList();
return new ListResult<ProjectDTO> { Data = data, TotalCount = projectList.TotalItemCount };
}
You're passing string as such:
public async Task<ListResult<ProjectDTO>> GetListedProjects(int pageSize, int pageNumber, string status)
{
var projects = await unitOfWork.ProjectRepository.Get();
//i cannot filter like this
projects.Where(x => x.Status == status);
var orderedProjects = projects.OrderBy(x => x.Name);
var projectList = orderedProjects.ToPagedList(pageNumber, pageSize);
var data = projectList.Select(x => ToDTO.ProjectBuild(x)).ToList();
return new ListResult<ProjectDTO> { Data = data, TotalCount = projectList.TotalItemCount };
}
When you should be passing the enum
public async Task<ListResult<ProjectDTO>> GetListedProjects(int pageSize, int pageNumber, Status status)
You could also pass in string, and create a Status variable and with some switch statement, you could set it to the correct enum. I don't like converting a string to an enum as some of the answers show. Error prone in my opinion, you're better off trying to either A) resolve the string to a Status or B) just pass in the Status enum to avoid any conflict.
Before you call projects.Where(x => x.Status == status); you should try and parse that string value of status into the Status Enum.
You can use either Enum.Parse which throws an exception if the parse fails or you can use Enum.TryParse that returns a bool based on the success/failure of the parsing operation.
You can easily convert the Status enum to String and filter it. You can even search by part of the Status name if you use the Contains method:
projects.Where(x => x.Status.ToString() == status); //To filter by exact input value
projects.Where(x => x.Status.ToString().Contains(status)); //To filter by part of the name search, for example if the input status is any of "N" or "Ne" or "New", the query returns the row with Status = 1

Modifying Expression<Func<T, bool>>

I haven't worked with expressions that much so I can't say if my intention makes any sense at all. I have looked around the interwebs and SO to no avail.
Say I have a method like so
public async Task<T> GetFirstWhereAsync(Expression<Func<T, bool>> expression)
{
// this would be a call to 3rd party dependency
return await SomeDataProvider
.Connection
.Table<T>()
.Where(expression)
.FirstOrDefaultAsync();
}
And from my-other-code I could be calling this e.g.
private async Task<User> GetUser(Credentials credentials)
{
return await SomeDataSource
.GetFirstWhereAsync(u => u.UserName.Equals(credentials.UserName));
}
So I'd be receiving first User from my SomeDataProvider that matches the given expression.
My actual question is how would I go about modifying GetFirstWhereAsync so that it would apply some SecretSauce to any expression passed to it? I could do this in caller(s) but that would be ugly and not much fun.
So if I pass in expressions like
u => u.UserName.Equals(credentials.UserName);
p => p.productId == 1;
I'd like these to be modified to
u => u.UserName.Equals(SecretSauce.Apply(credentials.UserName));
p => p.productId == SecrectSauce.Apply(1);
You can modify the expression inside method, but it's a bit complicated and you will need to do it case-by-case basis.
Example below will deal with modification from x => x.Id == 1 to x => x.Id == SecretSauce.Apply(1)
Class
class User
{
public int Id { get; set; }
public string Name { get; set;}
public override string ToString()
{
return $"{Id}: {Name}";
}
}
Sauce
class SquareSauce
{
public static int Apply(int input)
{
// square the number
return input * input;
}
}
Data
User[] user = new[]
{
new User{Id = 1, Name = "One"},
new User{Id = 4, Name = "Four"},
new User{Id = 9, Name = "Nine"}
};
Method
User GetFirstWhere(Expression<Func<User, bool>> predicate)
{
//get expression body
var body = predicate.Body;
//get if body is logical binary (a == b)
if (body.NodeType == ExpressionType.Equal)
{
var b2 = ((BinaryExpression)body);
var rightOp = b2.Right;
// Sauce you want to apply
var methInfo = typeof(SquareSauce).GetMethod("Apply");
// Apply sauce to the right operand
var sauceExpr = Expression.Call(methInfo, rightOp);
// reconstruct equals expression with right operand replaced
// with "sauced" one
body = Expression.Equal(b2.Left, sauceExpr);
// reconstruct lambda expression with new body
predicate = Expression.Lambda<Func<User, bool>>(body, predicate.Parameters);
}
/*
deals with more expression type here using else if
*/
else
{
throw new ArgumentException("predicate invalid");
}
return user
.AsQueryable()
.Where(predicate)
.FirstOrDefault();
}
Usage
Console.WriteLine(GetFirstWhere(x => x.Id == 2).ToString());
The method will turn x => x.Id == 2 to x => x.Id == SquareSauce.Apply(2) and will produce:
4: Four

Dynamic Linq to OrderBy Object Nested in IEnumerable

I am trying to write some dynamic linq to order by a property of a list item, for use with NHibernate
public class Company
{
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee
{
public string Name{get; set;}
public string PayrollNo{get; set;}
}
In this example it would be like to return all Companies and order by PayrollNumber.
The Repository Method would look like this with standard linq.
var companies = session.Query<Company>()
.OrderBy(x => x.Pieces.FirstOrDefault().PayrollNo)
.FetchMany(x => x.Employees)
I would like to change this to dynamic linq to order by column headers
var companies = session.Query<Company>()
.OrderByName("Employees.PayrollNo"), isDescending)
.FetchMany(x => x.Employees)
I took an approach similar to the answer in Dynamic LINQ OrderBy on IEnumerable<T> Writing an extension method
But then drilled down with recursion
public static IQueryable<T> OrderByName<T>(this IQueryable<T> source, string propertyName, Boolean isDescending)
{
if (source == null) throw new ArgumentNullException("source");
if (propertyName == null) throw new ArgumentNullException("propertyName");
var properties = propertyName.Split('.');
var type = GetNestedProperty(properties, typeof(T));
var arg = Expression.Parameter(type.GetProperty(properties.Last()).PropertyType, "x");
var expr = Expression.Property(arg, properties.Last());
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);
String methodName = isDescending ? "OrderByDescending" : "OrderBy";
object result = typeof(Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), type)
.Invoke(null, new object[] { source, lambda });
return (IQueryable<T>)result;
}
//Walk the tree of properties looking for the most nested in the string provided
static Type GetNestedProperty(string[] propertyChain, Type type)
{
if (propertyChain.Count() == 0)
return type;
string first = propertyChain.First();
propertyChain = propertyChain.Skip(1).ToArray(); //strip off first element
//We hare at the end of the hierarchy
if (propertyChain.Count() == 0)
return GetNestedProperty(propertyChain, type);
//Is Enumerable
if (type.GetProperty(first).PropertyType.GetInterfaces().Any(t => t.Name == "IEnumerable"))
return GetNestedProperty(
propertyChain,
type.GetProperty(first).PropertyType.GetGenericArguments()[0]);
return GetNestedProperty(
propertyChain,
type.GetProperty(first).PropertyType.GetProperty(propertyChain.FirstOrDefault()).GetType());
}
I am having difficulty generating the expression in the OrderByName extension method. I have tried quite a few things now, but the problem is that Payroll number does not exist in the Company class.
Is what I am trying to achieve even possible?
Any help with this would be greatly appreciated.
NHibernate has other query APIs which are better for this.
string property = "Employees.PayrollNo";
var query = session.QueryOver<Company>()
.Fetch(x => x.Employees).Eager;
// Join on the associations involved
var parts = property.Split('.');
var criteria = query.UnderlyingCriteria;
for (int i = 0; i < parts.Length - 1; i++)
{
criteria.CreateAlias(parts[i], parts[i]);
}
// add the order
criteria.AddOrder(new Order(property, !isDescending));
var companies = query.List();

Categories