【问题标题】:Strongly typed Linq filtering method强类型 Linq 过滤方法
【发布时间】:2013-08-04 06:42:33
【问题描述】:

我有点厌倦了像这样写几行服务层代码:

以下代码仅供读者参考。所以他们可能有错误或拼写错误,对此感到抱歉:)

//ViewModel
public class EntityIndexFilterPartial
{
    public int? Id { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public IEnumerable<SelectListItem> StatusList { get; set; }
    public int? StatusID { get; set; }
}


//Service Layer method
//Method parameters are reperesents view model properties
public IQueryable<Entity> FilterBy(int? id, DateTime? startDate, DateTime? endDate, int? statusId)
{
    var filter = _db.Entities.AsQueryable();
    if (id.HasValue)
        filter = filter.Where(x => x.Id == id.Value);
    if (startDate.HasValue)
        filter = filter.Where(x => x.StartDate >= startDate.Value);
    if (endDate.HasValue)
        filter = filter.Where(x => x.EndDate <= endDate.Value);
    if (statusId.HasValue)
        filter = filter.Where(x => x.EntityStatus.StatusID == statusId);
    return filter;
}

我搜索了一些智能设计的代码。我知道动态 LINQ 库,我也使用它。但我正在寻找强类型过滤。 我不想写魔术字符串或类似的东西。

所以基本上我找到了一些解决方案,但我想从这个社区听到一些写得很好的智能代码。当然可能有几十种解决方案,但你又将如何编写强类型过滤服务层代码。任何想法...

以下是我的一些解决方案:

解决方案 1: 相同的 FilterBy 方法但参数不同,现在采用表达式列表。所以这意味着我正在控制器中创建 predicateList 并将其发送到这里。

public IQueryable<Entity> FilterBy(List<Expression<Func<Entity,bool>>> predicateList)
{
    var filter = _db.Entities.AsQueryable();
    foreach (var item in predicateList)
    {
        filter = filter.FilterBy(item);
    }
    return filter;
}

解决方案 2: FilterBy 方法将 EntityIndexFilterPartial 作为应用程序服务层(不是域服务)中的参数。我确信这个设计有一些问题,但我想听听你的意见。

public IQueryable<Entity> FilterBy(EntityIndexFilterPartial filterPartial)
{
    //I'm calling the domain service layer FilterBy method such as like in solution 1.
}

解决方案 3: 我认为这个比其他的要好得多,但我仍在考虑一些更简单和更好的代码。

//helper class
public static class FilterByHelper
{
    public static IQueryable<T> If<T>(this IQueryable<T> filter, bool condition, Expression<Func<T, bool>> predicate)
    {
        if (condition)
            return filter.FilterBy(predicate);
        return filter;
    }

    public static IQueryable<T> FilterBy<T>(this IQueryable<T> filter, Expression<Func<T, bool>> predicate)
    {
        return filter.Where(predicate);
    }
}


public IQueryable<Entity> FilterBy(int? id, DateTime? startDate, DateTime? endDate, int? statusId)
{
    return _db.Entities
        .If(id.HasValue, x => x.Id == id.Value)
        .If(startDate.HasValue, x => x.StartDate >= startDate.Value)
        .If(endDate.HasValue, x => x.EndDate <= endDate.Value)
        .If(statusId.HasValue, x => x.EntityStatus.StatusID == statusId);
}

我知道这个问题有点长,但我希望我清楚地问我想问什么。

作为一个简单而快速的问题,您是否知道任何设计巧妙的代码可以让我们免于编写这些过滤代码的相同行?

顺便说一句,我不是在寻找设计模式解决方案或巨大的答案,你可以给我一些例子或者说如何找到更好的路径就足够了。

当然,如果你写了一个完整的解释性回复,我会很高兴的。

谢谢。

【问题讨论】:

  • 这是否与 FindAll(predicate) 方法的工作方式不同?

标签: c# linq strong-typing service-layer


【解决方案1】:

你尝试过简单的|| 条件吗?

return _db.Entities
    .Where(x => id == null || x.Id == id.Value)
    .Where(x => startDate == null || x.StartDate >= startDate.Value)
    .Where(x => endDate == null ||  x.EndDate <= endDate.Value)
    .Where(x => statusId == null || x => x.EntityStatus.StatusID == statusId);

?我希望在查询优化之后,无操作过滤器将等同于根本不添加过滤器。

【讨论】:

  • +1 这是个好技巧。您能否为服务层方法参数提出更好的解决方案。我关于摆脱这些参数的问题的一部分。谢谢你:)
  • @YusufUzun:对不起,我不太明白你的意思,恐怕。如果没有任何参数,您将如何通过过滤器?
  • @JonSkeet 好吧,我的错。在解决方案 2 中,我将 ViewModel 作为参数发送到我的服务层。但我们知道这不是一个好的架构设计,对吧?而且我不想像在解决方案 3 中那样将每个过滤属性都写成方法参数。你能为这种情况提出一些不同的建议吗?
  • @YusufUzun:我没有说那样的话。我的意思是你现在应该忽略实现,并计算出你希望 调用 代码的样子。你想表达过滤器,对吧?所以弄清楚你想表达它们的方式和位置,然后很可能会简单地实现。
  • @YusufUzun:我可能会向下发送一个完全不同的类型:用于过滤的类型专门。毕竟,您可能不想过滤所有属性,而且您也不想表达 ViewModel 本身 - 您是在尝试表达过滤条件。
【解决方案2】:

我通过使用流畅的扩展方法解决了这个问题。我觉得这是解决这类特定问题的一个很好的解决方案。使用流畅的风格定义过滤器的好处是,当您实际使用它时,它会生成一些真正可读的代码。这在实践中的作用是使您的服务层更具动态性。

例如

如果你有

public class User {
    public int Id {get;set;}
    public DateTime CreatedOn {get;set;}
    public string Name {get;set;}
    public DateTime BirthDate {get;set;}
}

你可以写一些类似的过滤器

public IQueryable<User> OlderThan(this IQueryable<User> users, DateTime olderThan){/*implementation*/}

public IQueryable<User> CreatedAfter(this IQueryable<User> users, DateTime createdAfter){/*implementation*/}

//or something more generic
public IQueryable<User> WhereName(this IQueryable<User> users, Expression<Func<string,bool>> nameQuery){/*implementation*/}

然后像这样把这些东西串起来:

users
    .CreatedAfter(new DateTime(2011,1,1))
    .OlderThan(new DateTime(1985,1,2))
    .WhereName(n=>n.StartsWith("L"));

这使您的过滤逻辑更加动态,而无需创建难以维护和复杂的包罗万象的过滤器

实际上这意味着

  • 过滤器可以非常简单
  • 您可以创建在内部应用另一个过滤器的聚合过滤器
  • 过滤器只执行一个目的
  • 他们可以拥有与业务相关的名称

在我的博客中,我通过一些真实的例子来讨论这种方法。 http://blog.staticvoid.co.nz/2013/2/25/a_case_for_generic_repositories

【讨论】:

  • +1 有趣的想法,所以您说的是为该域对象上的每个过滤创建一个流畅的扩展方法。但它不是让我们的代码更多。但是,它易于维护和理解。不幸的是,大多数开发人员不想编写这样的条件。你是在控制器还是在服务层使用这些方法?
  • 我接受了@JonSkeet 的回答,因为 cmets 让我找到了比我更好的方法。但这也是非常好的方法,我也喜欢这个。不幸的是,有很多开发人员不想为每个属性编写这些。我会尽量让它通用。谢谢你:)
  • @YusufUzun 我个人在控制器上直接使用这些,但是很多人不喜欢在该级别使用可查询对象,因此会在不同的层枚举集合。就代码量而言,我认为这种方法不会在物理上产生更多的代码,它只是以不同的方式分解。你最终会得到更多的方法,但每一个都会简单得多。无论哪种方式,它都是一种权衡,但这是我个人喜欢这样做的方式。
【解决方案3】:

你好 Yusuf 这篇文章可能对你有帮助。解决方案看起来很聪明,它实现了动态 where 条件来 linq 查询,看起来很简单。

http://amitech.co/amitech-lab/item/dynamically-add-conditions-in-linq

希望对你有帮助

【讨论】:

  • 嗨@hkoseoglu,这是另一个很好的用法。我也在我的项目中使用 PredicateBuilder。但它仍然没有回应我对这个问题的要求。谢谢提醒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-03
  • 2014-06-21
  • 2012-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-14
相关资源
最近更新 更多