Шаблон репозитория и спецификации
в настоящее время я создаю новый проект, и я столкнулся с несколькими вещами, где мне нужен небольшой вклад.
вот что я думаю:
Я хотел бы общий репозиторий
Я не хочу возвращать IQueryable из моего репозитория.
Я хотел бы инкапсулировать мои запросы в спецификации.
я реализовал спецификацию шаблон
Он должен быть легко проверяемым
теперь я немного застрял, и мой вопрос в том, какой способ будет самым элегантным способом вызова метода find с одной или несколькими спецификациями:
(свободно): bannerRepository.Find().IsAvailableForFrontend().IsSmallMediaBanner()
или выразите запросы как лямбда с моими спецификациями
(лямбда): bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)
или, может быть, какой-то совершенно другой способ? Самое главное, что парень, реализующий фронт MVC, должен иметь хороший интуитивный опыт работы с репозиторием.
то, что я надеюсь достичь, - это сохранить гибкость som в отношении возможности комбинировать спецификации и дать опыт "фильтрации" со спецификациями, но без утечки IQueryable контроллеру, но больше похоже на ISpecifiable, что позволяет только изменять запрос со спецификациями, а не с Linq. Но я только что вернулся к утечке логики запроса к контроллеру таким образом?
3 ответов
Я видел некоторые Fluent API, которые используют свойства для спецификаций, поэтому они не добавляют шум скобок клиентам.
bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()
Being Exec () метод для выполнения спецификаций против РЕПО.
но даже если вы не используете свойства, я бы пошел на fluent API, так как он имеет минимальный шум.
или, может быть, какие-то совершенно другим способом?
ну, на самом деле я не получаю точно вашу реализацию репозитория (например, какой будет метод .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-предложения (но это всего лишь вопрос этой случай, конечно). Наконец, я бы составил спецификацию " вне " интерфейса репозитория (используя fluent interface).
может быть, это просто наивная идея, но я нахожу это довольно просто. Надеюсь, это поможет.
лично я бы пошел с лямбда-путем. Это может быть из-за моей любви к лямбде, но она предоставляет много места для общей настройки репозитория.
учитывая следующее:
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" и конкретный класс, расширяющий абстрактный класс "репозиторий" и реализующий "IBannerInterface":
interface IBannerRepository : IRepository<Banner>
{
}
и соответствующий репозиторий для его реализации:
class BannerRepository : Repository<Banner>, IBannerRepository
{
}
Я бы предложил использовать этот подход, поскольку он дает вам большую гибкость а также достаточно силы, чтобы контролировать все крошечные сущности, которые у вас есть.
вызова этих методов будет очень легко таким образом:
BannerRepository _repo = new BannerRepository();
_repo.FindSingle(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner);
Да, это означает, что вы должны сделать некоторую работу, но это легче, ад для вас, чтобы изменить источник данных позже.
надеюсь, что это помогает!