UI层是否应该能够将lambda表达式传递到服务层而不是调用特定的方法?

10

我正在处理的ASP.NET项目有三个层:UI、BLL和DAL。我想知道UI是否可以将lambda表达式传递给BLL,或者UI应该传递参数,Service方法应该使用这些参数来构造lambda表达式?以下是显示这两种情况的示例类。

public class JobService 
{
    IRepository<Job> _repository;

    public JobService(IRepository<Job> repository) 
    {
        _repository = repository;
    }

    public Job GetJob(int jobID)
    {
        return _repository.Get(x => x.JobID == jobID).FirstOrDefault();
    }

    public IEnumerable<Job> Get(Expression<Func<Job, bool>> predicate)
    {
        return _repository.Get(predicate);
    }
}

对于上面的类,UI调用以下内容是否可以接受:

JobService jobService = new JobService(new Repository<Job>());
Job job = jobService.Get(x => x.JobID == 1).FirstOrDefault();
或者只允许调用GetJob(int jobID)?这只是一个简单的例子,我的问题是一般而言,UI层是否应该能够将lambda表达式传递到服务层而不是调用特定的方法?
6个回答

8
这是基于情况的主观判断。像这样传递谓词并不一定是错误的。但我认为应该把它视为轻微的坏味道。
如果使用lambda表达式可以将6个方法缩减为1个,那么这可能是一个好的选择。另一方面,如果你可以轻松地传递一个简单的类型,那么lambda语法就是一种不必要的复杂化。
在上面的例子中,不了解上下文,我更喜欢使用简单的整数参数。通常应该有一个基本方法只通过ID获取记录。也许还有其他一两个在应用程序中重复使用的方法。然后再考虑一个接受lambda的通用方法。
您还应该考虑一些人建议的规则:在UI层和业务层之间不要有任何使用lambda指定谓词的方法。(有些人认为,出于某种原因,您的存储库甚至不应该有这样的方法!)我不认为这应该成为一个铁律,但有很好的理由。您的业务层和数据层之间应该防止发生危险的查询。如果允许传递lambda,那么UI层的初级开发人员很容易指定可能真正损坏数据库的查询。 (例如,他们会针对未索引的字段执行大型查询,并使用LINQ-to-objects对结果集进行过滤,而不知道这有多么低效。)
像许多其他良好的实践一样,这将在某种程度上取决于范围。在我最近的大型应用程序中,我没有从UI层传递lambda语法到业务层。我的计划是大力投资于业务层,使其非常智能化。它拥有所有需要的带有简单类型的方法。事实上,它通常通过简单的域对象属性为您提供所需内容,根本没有参数。我的接口确保UI只能导致发生有效的查询,可能仅在UI层使用轻微的LINQ-to-Objects谓词。(即使是“搜索”页面也是如此。我的业务层接受一个具有受限可能性的条件对象,并确保进行有效的查询。)
现在,你说“层”,而不是“层”。所以这些都在同一个程序集中?另一个缺点是lambda很难(目前)序列化。因此,如果必须分离层,则会后悔使用它们。

4
这总是一个有争议的问题,即暴露表达式树(和)应该限制在哪个“层”中。
首先是显而易见的——允许将 lambda传入层中的主要好处显然是在查询中具有巨大的灵活性,而无需编写大量的GetXXXByYYY类型的方法。客户端层还可以通过使用.Include来直接控制连接以指定图检索的深度,并且可以在数据库中控制排序(.OrderBy)、分组(.GroupBy)、行限制(.Take),通常会比在内存中执行相同的操作获得性能收益。
缺点包括:
- 可测试性——因为接口非常开放,所以有任意数量的排列组合需要检查。 - 信任——您的层的客户端可以自由地执行针对您的数据库的任意查询,这可能会导致性能问题(即客户端可能执行错过所有索引的查询)和安全问题(检索他们不应该访问的数据)。 - 序列化——表达式树不能直接序列化,因此您无法将其暴露在例如WCF之类的BLL上(尽管它们可以被代理)。
如果您允许表达式树作为参数,则建议您使用返回IQueryable而不是IEnumerable的方法。这将允许查询被链接/聚合(例如,您可以在材料化之前稍后修改查询)。个人而言,我们不允许表达式树超出我们的BLL,因此我们不会将其扩展到我们的UI界面 - 在我看来,Expression<>谓词被视为实现Evan的specification模式的机制。

2

假设您认为它可能不合适的原因是它可能会违反BLL的封装性,那么我的答案是取决于具体情况

委托lambda表达式是基本语言结构,单纯使用它们并不会破坏封装性。

当然,有一个例外情况,即如果作为参数传递给方法的委托的签名暴露了不应该从调用方范围内直接访问的类型。让我举个例子:

// Bad
public IEnumerable<Job> Get(Action<SqlCommand> queryCommand);

// Good
public IEnumerable<Job> Get(Func<Job, bool> predicate);

在第一个示例中,方法签名通过以 SqlCommand 类型的形式向调用者公开实现细节来破坏封装。它还允许调用者通过将任何查询作为参数来指定过多的自由度,这是非常危险的。
另一方面,在第二个示例中,方法签名简单地公开了一个模型类,该模型类已经是层的公共接口的一部分。它不增加 API 的表面积,也不公开有关操作如何实现的任何详细信息。

2

我个人不太喜欢这样做。我猜这并不是错的,但它会产生一个灰色地带,让你不知道应该在哪里编写查询语句。我认为当有其他人进入我的应用程序时,所有的表达式都在一个漂亮的层次中,这样更容易浏览。


1

我不会这样做。这是不好的实践。虽然合法,但并不好。

你正在将DAL逻辑添加到UI中,使你的DAL成为一个包装器。你的UI应该只向BLL或DAL传递参数,而不执行获取数据的逻辑。


1

这只是我的个人意见,但你试图编写的内容看起来类似于LINQ表达式的编写方式,所以如果它对于LINQ足够好,为什么不用呢?

如果你认为这样可以让你的工作更轻松,那我认为你应该这样做。唯一需要注意的是确保它易于维护,即能够跟踪所有这些传递的lambda表达式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接