I have a piece of Linq that queries an EntityFramework context in my web controller and returns the result, as follows:
[HttpGet]
public IActionResult GetRoutingRules()
{
var query = (from rr in _context.RoutingRules
join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
join origin in _context.RoutingZones on rr.OriginZoneId equals origin.ZoneId
join hub in _context.RoutingHub on rr.HubId equals hub.HubId
select new RoutingRulesDto(rr) { DestinationZoneName = dest.ZoneName, OriginZoneName = origin.ZoneName, HubName = hub.HubName });
return Ok(query);
}
I want a new method that will take a "filter" object, where I can narrow down the results of the above. My filter object looks like this:
public class RoutingSearchFilterDto
{
public int BrandId { get; set; }
public int? ServiceType { get; set; }
public long? OriginZoneId { get; set; }
public long? DestinationZoneId { get; set; }
public int? RuleRanking { get; set; }
public bool? IsRuleActive { get; set; }
}
The minimum info that needs to be set in this class is BrandId. All other properties are options in the filter.
I need to write a new controller method that will utilise this, something like:
[HttpPost("filtered")]
public IActionResult GetFilteredRoutingRules([FromBody] RoutingSearchFilterDto filter)
{
...
}
How do I linq query on properties that could potentially be null? Essentially, a dynamic query depending on the properties set in the filter object.
NOTE: I want this to affect the select statement that the EF runs, not just let EF get all the data, then filter the data set - the point of this is to make the db call more efficient.
Filter object might be sent where BrandId = 1, IsRuleActive = 1. Equally, it could be BrandId = 1, ServiceType = 3 (and therefore IsRuleActive is null so shouldn't be in the linq where clause).
I've tried this:
var param = (Expression.Parameter(typeof(RoutingRules), "rr"));
Expression combinedExpr = null;
if (filter.BrandId != null)
{
var exp = Expression.Equal(Expression.Property(param, "BrandId"), Expression.Constant(filter.BrandId));
combinedExpr = exp;
}
if (filter.DestinationZoneId != null)
{
var exp = Expression.Equal(Expression.Property(param, "DestinationZoneId"), Expression.Constant(filter.DestinationZoneId));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.OriginZoneId != null)
{
var exp = Expression.Equal(Expression.Property(param, "OriginZoneId"), Expression.Constant(filter.OriginZoneId));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.EshopServiceType != null)
{
var exp = Expression.Equal(Expression.Property(param, "EshopServiceType"), Expression.Constant(filter.EshopServiceType));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.IsRuleActive != null)
{
var exp = Expression.Equal(Expression.Property(param, "IsRuleActive"), Expression.Constant(filter.IsRuleActive, typeof(bool?)));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (filter.RuleRanking != null)
{
var exp = Expression.Equal(Expression.Property(param, "RuleRanking"), Expression.Constant(filter.RuleRanking));
combinedExpr = (combinedExpr == null ? exp : Expression.AndAlso(combinedExpr, exp));
}
if (combinedExpr == null)
combinedExpr = Expression.Default(typeof(bool));
var compiled = Expression.Lambda<Func<RoutingRules, bool>>(combinedExpr, param).Compile();
var results = (from rr in _context.RoutingRules.Where(compiled)
join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
join origin in _context.RoutingZones on rr.OriginZoneId equals origin.ZoneId
join hub in _context.RoutingHub on rr.HubId equals hub.HubId
where rr.BrandId == 21
select new RoutingRulesDto(rr) { DestinationZoneName = dest.ZoneName, OriginZoneName = origin.ZoneName, HubName = hub.HubName });
But the Where clause isn't applied to the generated Sql, it seems to pull back all records, then apply the where in memory, which isn't what I need.
Thanks in advance for any pointers!!