简体   繁体   English

存储库和规范模式

[英]Repository and Specification pattern

I'm currently setting up a new project, and I have run into a few things, where I need a little input. 我目前正在建立一个新项目,我遇到了一些问题,我需要一点点输入。

This is what i'm considering: 这就是我在考虑的问题:

  • I would like a generic repository 我想要一个通用的存储库

  • I don't want to return IQueryable from my repository. 我不想从我的存储库返回IQueryable。

  • I would like to encapsulate my queries in specifications. 我想将我的查询封装在规范中。

  • I have implemented the specification pattern 我已经实现了规范模式

  • It needs to be easily testable 它需要易于测试

Now this is where I get a little stuck and my question is which way would be the most elegant way of calling the find method with one or more specifications: 现在这是我陷入困境的地方,我的问题是哪种方式是使用一个或多个规范调用find方法的最优雅方式:

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

or express queries as lambdas with my specifications 或者用我的规范将查询表达为lambdas

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

or maybe some completely other way? 或者可能是其他一些方式? Most important thing is, that the guy implementing the MVC front, should have a good intuitive experience of the repository. 最重要的是,实现MVC前端的人应该具有良好的存储库直观体验。

What I am hoping to achieve is to keep som flexibility with regard to being able to combine specifications, and give the experience of "filtering" with the specfications, but without leaking an IQueryable to the controller, but more like an ISpecifiable, that only allows to modify the query with specifications and not with Linq. 我希望实现的是保持som灵活性,以便能够结合规范,并提供“过滤”的经验与规范,但不会泄漏IQueryable到控制器,但更像是一个ISpecifiable,只允许使用规范而不是Linq修改查询。 But am i just back at leaking query logic to the controller this way? 但我是否只是以这种方式将查询逻辑泄漏给控制器?

or maybe some completely other way? 或者可能是其他一些方式?

Well, actually I don't get exactly your repository implementation (eg what will the method .Find() return?), but I would choose another direction: 好吧,实际上我没有得到你的存储库实现(例如.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;
    }
}

So I have an entity, its specification interface and several implementors involved in a visitor pattern, its repository interface accepting a specification interface and its repository implementation, accepting a visitor capable to translate specifications into SQL clauses (but it's just a matter of this case, of course). 所以我有一个实体,它的规格接口,并参与了访问者模式的几个实现者,其仓库接口接受规范接口及其仓库实现,接受能力翻译规范到SQL子句访客(但它只是一个本案的事情,当然)。 Finally, I would compose specification "outside" the repository interface (using fluent interface). 最后,我将在存储库接口“外部”编写规范(使用流畅的接口)。

Maybe this is just a naive idea, but I find it quite straightforward. 也许这只是一个天真的想法,但我发现它很简单。 Hope this helps. 希望这可以帮助。

I have seen some Fluent API's that uses Properties for specifications, so they don't add the parenthesis noise to the clients. 我已经看到一些Fluent API使用Properties作为规范,因此它们不会将括号噪声添加到客户端。

bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()

Being Exec() a method for executing the specifications against the repo. 作为Exec()执行针对repo的规范的方法。

but even if you don't use the properties, I would go for the fluent API, since it has the minimum noise. 但即使你不使用这些属性,我也会选择流畅的API,因为它的噪音最小。

Personally I would go with the lambda way. 就个人而言,我会采用lambda方式。 It may be because of my love for lambda but it provides lot's of space for a generic repository setup. 这可能是因为我对lambda的热爱,但它为通用存储库设置提供了大量空间。

Considering the following: 考虑以下因素:

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

I don't know what your pattern looks like but you could refactor some things here: 我不知道你的模式是什么样的,但你可以在这里重构一些东西:

Create a generic interface called 'IRepository' of type containing all the methods for data access. 创建一个名为“IRepository”的通用接口,其类型包含数据访问的所有方法。

It could look like this: 它可能看起来像这样:

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

    T FindSingle(Func<T, bool> exp);
}   

Create an abstract 'Repository' class implementing this interface: 创建一个实现此接口的抽象“Repository”类:

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

We can now create an interface for the banners table/objects which implements our 'IRepository' and a concrete class extending the abstract 'Repository' class and implementing the 'IBannerInterface': 我们现在可以为实现我们的'IRepository'的横幅表/对象创建一个接口,并为扩展抽象'Repository'类和实现'IBannerInterface'的具体类创建:

interface IBannerRepository : IRepository<Banner>
{
}

And the matching repository to implement it: 以及用于实现它的匹配存储库:

class BannerRepository : Repository<Banner>, IBannerRepository
{
}

I would suggest using this approach as it gives you a lot of flexibility as well as enough power to control all the tiny entities you have. 我建议使用这种方法,因为它为您提供了很大的灵活性以及足够的能力来控制您拥有的所有微小实体。

Calling those methods will be super easy that way: 调用这些方法将非常简单:

BannerRepository _repo = new BannerRepository();

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

Yes, it means that you have to do some work but it is hell easier for you to change the data source later on. 是的,这意味着您必须做一些工作,但以后更容易更改数据源。

Hope it helps! 希望能帮助到你!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM