【问题标题】:Linq where clause using filter object使用过滤器对象的 Linq where 子句
【发布时间】:2018-03-10 05:01:23
【问题描述】:

我有一块 Linq 在我的 web 控制器中查询一个 EntityFramework 上下文并返回结果,如下所示:

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

我想要一个采用“过滤器”对象的新方法,我可以在其中缩小上述结果的范围。我的过滤器对象如下所示:

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

需要在这个类中设置的最少信息是 BrandId。所有其他属性都是过滤器中的选项。

我需要编写一个新的控制器方法来利用它,例如:

[HttpPost("filtered")]
public IActionResult GetFilteredRoutingRules([FromBody] RoutingSearchFilterDto filter)
{
    ...
}

如何对可能为空的属性进行 linq 查询?本质上,动态查询取决于过滤器对象中设置的属性。

注意:我希望这会影响 EF 运行的 select 语句,而不仅仅是让 EF 获取所有数据,然后过滤数据集 - 这样做的目的是使 db 调用更高效。

过滤器对象可能在 BrandId = 1、IsRuleActive = 1 的地方发送。同样,它可能是 BrandId = 1、ServiceType = 3(因此 IsRuleActive 为 null,因此不应在 linq where 子句中)。

我试过了:

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

但是Where子句并没有应用到生成的Sql上,好像是把所有记录拉回来,然后应用到内存中的where,这不是我需要的。

提前感谢您的任何指点!

【问题讨论】:

  • 你没有在任何地方使用编译后的表达式。

标签: c# entity-framework linq expression


【解决方案1】:

可以为此构建一个表达式树,但您是否考虑过:

IQueryable<...> query = ...;

if (routingSearchFilter.ServiceType != null)
  query = query.Where(e => e.ServiceType == routingSearchFilter.ServiceType);

if (...) 
   query = query.Where(....);

EF 引擎足够聪明,可以组合 Where 子句(当然还有 AND)。

编辑:

不清楚您是要过滤连接的结果还是只过滤第一个表。在那种情况下,它会继续像

var result = (from rr in query
          join dest in _context.RoutingZones on rr.DestinationZoneId equals dest.ZoneId
          join ...
          select new RoutingRulesDto(rr) .... ).ToSometing();

但我对 RoutingRulesDto(rr) 构造函数参数有点警惕。

【讨论】:

  • 更加简单易懂。您将逐步构建查询,并且在您开始迭代项目之前不会执行它。 EF 将创建正确的 where 子句,它不会获取所有数据并将其过滤到内存中。
  • 对不起,让我澄清一下 - 这个解决方案非常适合动态地将 WHERE 子句添加到 sql 中,但我已经失去了使用这种格式加入的能力。生成的 sql 应该类似于“SELECT t1.a,t1.b,t2.a FROM table1 t1 JOIN table2 t2 ON t2.Id = t1.FkId WHERE ”。上面的解决方案给出了除了连接之外的所有内容。
  • 好的,伙计们 - 弄清楚如何使用以下代码填充其他字段(没有连接):IQueryable query = _context.RoutingRules.Include(x => x.Hub).Include( y => y.DestinationZone).Include(z => z.OriginZone);
【解决方案2】:

如果您使用 LINQ 的 fluent API,您可以有条件地添加 Where 子句。

var query = _content.RoutingRules.Where(r => r.BrandId == filter.BrandId);
if (filter.OriginZoneId != null) {
    query = query.Where(r => r.OriginZoneId == filter.OriginZoneId);
}
if (filter.EshopServiceType != null) {
    query = query.Where(r => r.EshopServiceType == filter.EshopServiceType);
}
// etc...
var result = query.ToArray();

【讨论】:

    【解决方案3】:

    我的最终解决方案是黑白的,这就是我最后得到的:

    [HttpPost("filtered")]
    public IActionResult GetFilteredRoutingRules([FromBody] RoutingSearchFilterDto filter)
    {
        // Query to be build on the routing rules table.
        IQueryable<RoutingRules> query = _context.RoutingRules;
    
        // Populate the linked foreign key entities.
        query.Include(x => x.Hub).Include(y => y.DestinationZone).Include(z => z.OriginZone);
    
        // Build dynamic where statements.
        if (filter.BrandId != null)
            query = query.Where(r => r.BrandId == filter.BrandId);
    
        if (filter.OriginZoneId != null)            
            query = query.Where(r => r.OriginZoneId == filter.OriginZoneId);
    
        if (filter.DestinationZoneId != null)
            query = query.Where(r => r.DestinationZoneId == filter.DestinationZoneId);
    
        if (filter.IsRuleActive != null)
            query = query.Where(r => r.IsRuleActive == filter.IsRuleActive);
    
        if (filter.RuleRanking != null)
            query = query.Where(r => r.RuleRanking == filter.RuleRanking);
    
        // If you want to add paging:
        query = query.Skip(filter.PageSize * filter.PageNumber).Take(filter.PageSize);
    
        // Perform select on the table and map the results.
        var result = query.Select(r => new RoutingRulesDto
        {
            RoutingRuleId = r.RoutingRuleId,
            BrandId = r.BrandId,
            LastMileCarrierCode = r.LastMileCarrierCode,
            CashOnDelivery = r.CashOnDelivery,
            CreationTime = r.CreationTime,
            CurrencyCode = r.CurrencyCode,
            CurrencyDescription = Enum.Parse(typeof(Enumerations.CurrencyCode), r.CurrencyCode),
            DestinationZoneId = r.DestinationZoneId,
            EddFromDay = r.EddFromDay,
            EddToDay = r.EddToDay,
            ServiceType = r.ServiceType,
            ServiceTypeName = Enum.Parse(typeof(Enumerations.ServiceType), r.EshopServiceType),
            IsPickUpAvailable = r.IsPickUpAvailable,
            LastUpdateTime = r.LastUpdateTime,
            LastUpdateUser = r.LastUpdateUser,
            OriginZoneId = r.OriginZoneId,
            RuleRanking = r.RuleRanking,
            SignOnDelivery = r.SignOnDelivery,
            TermsOfDelivery = r.TermsOfDelivery,
            TermsOfDeliveryName = Enum.Parse(typeof(Enumerations.TermsOfDelivery), r.TermsOfDelivery),
            ValueOfGoods = r.ValueOfGoods,
            WeightLowerLimit = r.WeightLowerLimit,
            WeightUpperLimit = r.WeightUpperLimit,
            FirstMileCarrierCode = r.FirstMileCarrierCode,
            HubId = r.HubId,
            IsInsuranceAvailable = r.IsInsuranceAvailable,
            IsRuleActive = r.IsRuleActive,
            HubName = r.Hub.HubName,
            DestinationZoneName = r.DestinationZone.ZoneName,
            OriginZoneName = r.OriginZone.ZoneName,
        });
    
        // The SQL produced includes the joins and where clauses as well as only 
        // selecting the column names that are required in the flattened return object.
    
        return Ok(result);
    }
    

    感谢大家的帮助!

    【讨论】:

      猜你喜欢
      • 2021-08-01
      • 2015-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多