仓储和规约模式

6

我目前正在设置一个新项目,遇到了一些问题,需要一些帮助。

这是我考虑的事情:

  • 我想要一个通用的仓储库

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

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

  • 我已经实现了规范模式

  • 它需要易于测试

现在我有点困惑,我的问题是使用一个或多个规范调用查找方法的最优雅的方法是哪种:

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

或者将查询表达为带有我的规范的lambda表达式

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

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

我希望实现的目标是保持一些灵活性,以便能够组合规范,并通过规范给出“过滤”的体验,但不会向控制器泄漏IQueryable,而更像是一个ISpecifiable,它只允许使用规范修改查询而不是Linq。但是这样做我是否又回到了向控制器泄漏查询逻辑的问题?

3个回答

2

我见过一些使用属性来规范的流畅API,因此它们不会给客户端添加括号噪音。

bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()

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

即使您不使用属性,我仍然建议使用流畅的API,因为它具有最小的干扰。


1
+1 我最终做了这样的事情。这个项目是用VB.Net编写的,所以括号不是问题,我可以省略它们。我从我的查找中返回一个规范对象,其中包含传递给它的IQueryable,并通过一些表达式操作将Ands或Ors的规范组合在一起,最后执行。 - Luhmann

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子句的访问者(但这只是这种情况下的问题)。最后,我将在存储库接口“外部”组合规范(使用流畅的接口)。
也许这只是一个天真的想法,但我觉得它非常直接。希望这能帮助到你。

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

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

是的,这意味着你需要做一些工作,但以后更改数据源对你来说会容易得多。

希望能帮到你!


谢谢你的回答。我已经掌握了仓库位元 - 与你的解决方案很相似。我需要更多的输入关于如何使用所提到的语法实现规范模式。 - Luhmann
目前,流畅的方法被广泛使用,可能是许多人首选和支持的方式。但是,就我个人而言,我会选择Lambda方式,因为对我来说它更易读且看起来更整洁。但这只是我的个人偏好 :P - Faizan S.
我使用了与Shaharyar在这里提出的方法非常相似的方法。我遇到的大问题是可测试性。在我的单元测试中,我仍然没有找到一种验证特定linq表达式是否被调用的方法。我使用NSubstitute,但它无法做到这一点。我也研究了Moq,但也找不到一种方法来实现这一点。 - Ozzy
使用此方法,您将加载整个表并在内存中进行过滤,因为Func<T, bool>无法翻译成"存储器"(Where(Func<T, bool>)方法是IEnumerable<T>而不是IQueryable<T>的扩展方法)。您应该使用Expression<Func<T, bool>>以便使用能够将条件翻译成存储供应商的IQueryable<T>.Where(Expression<Func<T, bool>>)方法。 - nflash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接