【问题标题】:Advice With Repository/Service Layer Design Pattern存储库/服务层设计模式的建议
【发布时间】:2010-08-25 00:39:55
【问题描述】:

在这里尝试创建一个非常简单的存储库和服务层模式。 (.NET 4、C#、LINQ,尽管这个问题部分与语言无关)。注意:这只是研发。

我的目标是尽量减少服务层中方法定义的数量。

这是我的存储库合同:

interface IFooRepository
{
   IEnumerable<Foo> Find();
   void Insert(Foo foo);
   void Update(Foo foo);
   void Delete(Foo foo);
}

没有什么新东西。

现在,这是我(尝试)在我的服务合同中的内容:

interface IFooDataService
{
   public IEnumerable<Foo> Find(FooSearchArgs searchArgs);
}

基本上,任何特定的“Foo”都有许多属性(id、名称等),我希望能够对其进行搜索。

所以,我不想为每个不同的属性使用 1x Find 方法,我只想要一个 - 这样当我创建额外的属性时我不必修改合同。

“FooSearchArgs”只是一个简单的 POCO,它具有所有不同的“Foo”属性。

所以,这就是我想要做的,这是我的问题:

  • 这是糟糕的设计吗?如果有,有哪些替代方案?
  • 如何在服务层实现这种过滤?我是否必须检查设置了“FooSearchArgs”的哪些属性,然后继续过滤? (如果是这样,那么 query.where,如果是这样,query.where 等) 任何人都知道一个聪明的 LINQ IEnumerable 扩展方法来做到这一点? (即repository.WhereMeetsSearchCriteria(fooSearchArgs)

感谢您的帮助。

【问题讨论】:

    标签: c# linq extension-methods repository-pattern service-layer


    【解决方案1】:

    我们使用非常相似的东西。您需要决定的一件事是您是否要在存储库之外公开 IQueryable。您的 find 方法返回 IEnumerable,它可能是您的 when 子句返回的 IQueryable。

    返回 IQueryable 的好处是您可以在存储库层之外进一步细化您的标准。

    repository.Find(predicate).Where(x => x.SomeValue == 1);
    

    只有当你使用返回的数据时才会编译表达式,这就是缺点。因为您只有在实际使用结果时才访问数据库,所以您最终可能会在会话(nhibernate)或连接关闭后尝试调用数据库。

    我个人的偏好是使用规范模式,在该模式中,您将 find 方法传递给 ISpecification 对象用于执行查询。

    public interface ISpecification<TCandidate>
    {
        IQueryable<TCandidate> GetSatisfyingElements(IQueryable<TCandidate> source);
    }
    
    public class TestSpecification : ISpecification<TestEntity>
    {
        public IQueryable<TestEntity> GetSatisfyingElements(IQueryable<TestEntity> source)
        {
            return source.Where(x => x.SomeValue == 2);
        }
    }
    
    public class ActiveRecordFooRepository: IFooRepository
    {
        ...
    
        public IEnumerable<TEntity> Find<TEntity>(ISpecification<TEntity> specification) where TEntity : class 
        {
            ...
    
            return specification.GetSatisfyingElements(ActiveRecordLinq.AsQueryable<TEntity>()).ToArray();
    
            ...
        }
    
        public TEntity FindFirst<TEntity>(ISpecification<TEntity> specification) where TEntity : class 
        {
            return specification.GetSatisfyingElements(ActiveRecordLinq.AsQueryable<TEntity>()).First();
        }
    }
    

    查询运行后,存储库调用从规范返回的结果 IQueryable 上的 ToArray 或 ToList,以便查询在此处进行评估。虽然这似乎不如公开 IQueryable 灵活,但它有几个优点。

    1. 查询会立即执行,并防止在会话关闭后调用数据库。
    2. 因为您的查询现在被捆绑到规范中,所以它们是可单元测试的。
    3. 规范是可重复使用的,这意味着您在尝试运行类似查询时没有代码重复,并且查询中的任何错误只需在一个地方修复。
    4. 通过正确的实施方式,您还可以将规范链接在一起。

    repository.Find(
        firstSpecification
            .And(secondSpecification)
            .Or(thirdSpecification)
            .OrderBy(orderBySpecification));
    

    【讨论】:

    • 一个有趣的镜头。但是我更喜欢使用延迟执行 (LINQ),因为它可以更好地控制查询(允许我在服务层中构建它们)。使用另一种设计模式来解决这个问题(即 UnitOfWork),封闭会话调用不是问题。感谢您的回答(+1)-我阅读了规范模式。
    • 你可能会觉得这很有趣:huyrua.wordpress.com/2010/07/13/… 这是一个如何使用规范和工作单元模式实现持久性无知存储库的示例。
    • 还有这个:kitchaiyong.net/2009/10/…。这个更深入一点。
    • 我最终应用了您所说的规范模式(使用您发给我的第二个链接,这绝对是黄金)。工作得很好,并且仍然可以延迟执行。再次感谢。
    【解决方案2】:

    是否将Func 作为参数传递给服务层的Find 方法,而不是FooSearchArgs,这是一个选项? Enumerables 有一个 Where 方法 (linq),它将 Func 作为参数,因此您可以使用它来过滤结果。

    【讨论】:

    • 这是一个选项,是的——我还在学习表达式树/lambdas。你有没有机会用一个例子来扩展你的答案(与我上面的问题有关)?谢谢
    • 这可能是低效的,但如果不使用 Linq,您可能会从数据库返回比所需更多的结果。
    • @Craig 我正在使用 LINQ。目前只有一个假存储库,但真正的存储库将是 L2SQL 或 EF。
    • 类似 sLayerInstance.Find(theFoo => theFoo.id%2 == 0) 的东西。它没有做任何有趣的事情,但它是你可以做的一个例子。然后您可以使用它来过滤存储库 Find()。
    • 我知道如何将谓词传递给方法,我更多的是寻求指导以实际实现该方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-26
    • 2017-09-19
    • 2016-10-07
    • 2012-11-26
    • 2013-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多