6

我目前正在建立一个新项目,并且遇到了一些事情,我需要一些输入。

这就是我正在考虑的:

  • 我想要一个通用存储库

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

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

  • 我已经实现了规范模式

  • 它需要易于测试

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

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

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

(拉姆达):bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)

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

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

4

3 回答 3

2

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

bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()

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

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

于 2010-02-08T12:34:04.187 回答
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 子句的访问者(但这只是这种情况的问题,当然)。最后,我将在存储库接口“外部”编写规范(使用流利的接口)。

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

于 2013-09-04T16:41:32.257 回答
1

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

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

希望能帮助到你!

于 2010-02-08T13:12:56.230 回答