存储库和规范模式

发布于 2024-08-20 19:39:01 字数 733 浏览 5 评论 0原文

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

这就是我正在考虑的:

  • 我想要一个通用存储库

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

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

  • 我已经实现了规范模式

  • 它需要易于测试

现在这就是我遇到一点困难的地方,我的问题哪种方式是使用一个或多个规范调用 find 方法的最优雅的方式:(

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

或将查询表达为 lambda我的规范

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

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

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

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.

  • 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:

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

or express queries as lambdas with my specifications

(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.

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. But am i just back at leaking query logic to the controller this way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

决绝 2024-08-27 19:39:01

我见过一些使用 Properties 进行规范的 Fluent API,因此它们不会向客户端添加括号噪音。

bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()

Exec() 是一种针对存储库执行规范的方法。

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

I have seen some Fluent API's that uses Properties for specifications, so they don't add the parenthesis noise to the clients.

bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()

Being Exec() a method for executing the specifications against the repo.

but even if you don't use the properties, I would go for the fluent API, since it has the minimum noise.

只是一片海 2024-08-27 19:39:01

或者也许是其他完全不同的方式?

好吧,实际上我并没有完全了解您的存储库实现(例如,方法 .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 子句的访问者(当然,这只是这个情况的问题) 。最后,我将在存储库接口“外部”编写规范(使用流畅的接口)。

也许这只是一个天真的想法,但我发现它很简单。
希望这有帮助。

or maybe some completely other way?

Well, actually I don't get exactly your repository implementation (e.g. what will the method .Find() return?), but I would choose another direction:

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). 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.

情徒 2024-08-27 19:39:01

就我个人而言,我会选择 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);
}   

创建一个实现此接口的抽象“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);
    }
}

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

interface IBannerRepository : IRepository<Banner>
{
}

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

class BannerRepository : Repository<Banner>, IBannerRepository
{
}

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

这样调用这些方法将非常简单:

BannerRepository _repo = new BannerRepository();

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

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

希望有帮助!

Personally I would go with the lambda way. It may be because of my love for lambda but it provides lot's of space for a generic repository setup.

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.

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:

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':

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!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文