【问题标题】:Repository and Specification pattern存储库和规范模式
【发布时间】:2011-01-14 08:26:24
【问题描述】:

我目前正在建立一个新项目,我遇到了一些事情,我需要一些意见。

这是我正在考虑的:

  • 我想要一个通用存储库

  • 我不想从我的存储库中返回 IQueryable。

  • 我想将我的查询封装在规范中。

  • 我已经实现了规范模式

  • 它需要易于测试

现在这是我有点卡住的地方,我的问题是哪种方式是使用一个或多个规范调用 find 方法的最优雅方式:

(流利):bannerRepository.Find().IsAvailableForFrontend().IsSmallMediaBanner()

或者用我的规范将查询表达为 lambdas

(Lambda):bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)

或者完全是其他方式?最重要的是,实现 MVC 前端的人应该对存储库有良好的直观体验。

我希望实现的是在能够组合规范方面保持一定的灵活性,并提供与规范“过滤”的体验,但不会将 IQueryable 泄漏给控制器,但更像是 ISpecifiable,只允许使用规范而不是 Linq 修改查询。但是我只是回到以这种方式将查询逻辑泄漏给控制器吗?

【问题讨论】:

    标签: c# .net repository specification-pattern


    【解决方案1】:

    我见过一些 Fluent API 使用属性作为规范,因此它们不会向客户端添加括号噪音。

    bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()
    

    作为 Exec() 一种针对 repo 执行规范的方法。

    但即使你不使用属性,我也会选择流畅的 API,因为它的噪音最小。

    【讨论】:

    • +1 我最终做了这样的事情。这个项目在 VB.Net 中,所以括号不是问题,我可以省略它们。我从我的 Find 返回一个 f 规范对象,该对象传递了 IQueryable,并通过一些表达式操作 Ands 或 Ors 将规范放在一起,最后执行。
    【解决方案2】:

    或者完全不同的方式?

    好吧,实际上我并没有完全了解您的存储库实现(例如,.Find() 方法将返回什么?),但我会选择另一个方向:

    public class Foo 
    { 
        public Int32 Seed { get; set; }
    }
    
    public interface ISpecification<T> 
    {
        bool IsSatisfiedBy(T item);
    }
    
    public interface IFooSpecification : ISpecification<Foo> 
    {
        T Accept<T>(IFooSpecificationVisitor<T> visitor);
    }
    
    public class SeedGreaterThanSpecification : IFooSpecification
    {
        public SeedGreaterThanSpecification(int threshold)
        {
            this.Threshold = threshold;
        }
        public Int32 Threshold { get; private set; }
        public bool IsSatisfiedBy(Foo item) 
        {
            return item.Seed > this.Threshold ;
        }
        public T Accept<T>(IFooSpecificationVisitor<T> visitor)
        {
            return visitor.Visit(this);
        }
    }
    public interface IFooSpecificationVisitor<T>
    {
        T Visit(SeedGreaterThanSpecification acceptor);
        T Visit(SomeOtherKindOfSpecification acceptor);
        ...
    }
    public interface IFooRepository 
    {
        IEnumerable<Foo> Select(IFooSpecification specification);
    }
    public interface ISqlFooSpecificationVisitor : IFooSpecificationVisitor<String> { }
    public class SqlFooSpecificationVisitor : ISqlFooSpecificationVisitor
    {
        public string Visit(SeedGreaterThanSpecification acceptor)
        {
            return "Seed > " + acceptor.Threshold.ToString();
        }
        ...
    }
    public class FooRepository
    {   
        private ISqlFooSpecificationVisitor visitor;
    
        public FooRepository(ISqlFooSpecificationVisitor visitor)
        {
            this.visitor = visitor;
        }
    
        public IEnumerable<Foo> Select(IFooSpecification specification)
        {
            string sql = "SELECT * FROM Foo WHERE " + specification.Accept(this.visitor);
            return this.DoSelect(sql);
        }
    
        private IEnumerable<Foo> DoSelect(string sql)
        {
            //perform the actual selection;
        }
    }
    

    所以我有一个实体,它的规范接口和几个涉及访问者模式的实现者,它的存储库接口接受规范接口及其存储库实现,接受能够将规范转换为 SQL 子句的访问者(但这只是一个问题这个案例,当然)。最后,我将在存储库接口“外部”编写规范(使用流畅的接口)。

    也许这只是一个幼稚的想法,但我觉得它很简单。 希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      我个人会选择 lambda 方式。这可能是因为我喜欢 lambda,但它为通用存储库设置提供了很多空间。

      考虑以下几点:

      bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)
      

      我不知道你的模式是什么样的,但你可以在这里重构一些东西:

      创建一个名为“IRepository”的通用接口,其类型包含所有数据访问方法。

      它可能看起来像这样:

      interface IRepository<T> where T : class
      {
          IEnumerable<T> FindAll(Func<T, bool> exp);
      
          T FindSingle(Func<T, bool> exp);
      }   
      

      创建一个实现此接口的抽象“存储库”类:

      class Repository<T> : IRepository<T> where T : class
      {
          TestDataContext _dataContext = TestDataContext();
      
          public IEnumerable<T> FindAll(Func<T, bool> exp)
          {
              _dataContext.GetTable<T>().Where<T>(exp);
          }
      
          public T FindSingle(Func<T, bool> exp)
          {
              _dataContext.GetTable<T>().Single(exp);
          }
      }
      

      我们现在可以为实现我们的“IRepository”的横幅表/对象创建一个接口,以及一个扩展抽象“Repository”类并实现“IBannerInterface”的具体类:

      interface IBannerRepository : IRepository<Banner>
      {
      }
      

      以及实现它的匹配存储库:

      class BannerRepository : Repository<Banner>, IBannerRepository
      {
      }
      

      我建议使用这种方法,因为它为您提供了很大的灵活性以及足够的能力来控制您拥有的所有微小实体。

      这样调用这些方法会超级简单:

      BannerRepository _repo = new BannerRepository();
      
      _repo.FindSingle(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner);
      

      是的,这意味着您必须做一些工作,但以后更改数据源会更容易。

      希望对你有帮助!

      【讨论】:

      • 感谢您的回答。我已经涵盖了存储库位 - 与您的解决方案不同。我一直在寻找的是更多关于如何使用上述语法进行规范模式的输入。
      • 目前 fluent 方法使用非常广泛,可能很多人都喜欢和支持。但如前所述,我个人会使用 Lambda 方式,因为对我来说它确实更具可读性并且看起来更整洁。但这只是我的偏好:P
      • 我使用了与您在这里提出的 Shaharyar 非常相似的方法。我遇到的最大问题是可测试性。我仍然没有在我的单元测试中找到一种方法来验证是否调用了特定的 linq 表达式。我使用 NSubstitute,但它不能做到这一点。我也查看了起订量,但找不到这样做的方法。
      • 使用这种方法,您正在加载整个表并将其过滤到内存中,因为 Func 无法转换为“存储”(方法 Where(Func) 是 IEnumerable 而不是 IQueryable) 的扩展方法。您应该使用 Expression> 以便使用 IQueryable.Where(Expression>) 可以将条件转换为商店提供程序的方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-20
      • 2023-04-10
      • 1970-01-01
      • 2010-12-02
      • 2013-08-13
      • 1970-01-01
      • 2016-04-26
      相关资源
      最近更新 更多