我目前正在建立一个新项目,我遇到了一些问题,我需要一点点投入。
这就是我正在考虑的事情:
我想要一个通用存储库
我不想从我的存储库返回IQueryable。
我想将我的查询封装在规范中。
我已实施规范模式
它需要易于测试
现在这就是我遇到的问题,我的问题是以哪种方式调用具有一个或多个规范的find方法是最优雅的方式:
(流利):bannerRepository.Find().IsAvailableForFrontend().IsSmallMediaBanner()
或使用我的规范将查询表达为lambdas
(Lambda):bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)
或者可能是其他完全不同的方式?最重要的是,实现MVC前端的人应该具有良好的存储库直观体验。
我希望实现的目标是保持灵活性,使其能够结合规格,并提供“过滤”规格的体验,但不会将IQueryable泄漏给控制器,但更像是一个可规范的,只允许使用规范修改查询,而不允许使用Linq修改查询。但我是否只是以这种方式向控制器泄漏查询逻辑?
答案 0 :(得分:2)
我见过一些使用属性作为规范的Fluent API,因此它们不会将括号噪声添加到客户端。
bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()
使Exec()成为执行针对repo的规范的方法。
但即使你不使用这些属性,我也会选择流畅的API,因为它的噪音最小。
答案 1 :(得分: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子句的访问者(但它只是一个问题此案例,当然)。最后,我将在存储库接口“外部”编写规范(使用流畅的接口)。
也许这只是一个天真的想法,但我发现这很简单。 希望这会有所帮助。
答案 2 :(得分: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);
是的,这意味着您必须做一些工作,但以后更容易更改数据源。
希望它有所帮助!