Репозиторий и шаблон спецификации
-
19-09-2019 - |
Вопрос
В настоящее время я настраиваю новый проект, и я столкнулся с несколькими вещами, в которых мне нужен небольшой вклад.
Это то, что я рассматриваю:
Мне бы хотелось иметь общий репозиторий
Я не хочу возвращать IQueryable из моего репозитория.
Я хотел бы инкапсулировать свои запросы в спецификации.
Я реализовал шаблон спецификации
Это должно быть легко тестируемо
Теперь вот где я немного застрял, и мой вопрос заключается в том, какой способ был бы наиболее элегантным способом вызова метода find с одной или несколькими спецификациями:
(Свободно): bannerRepository.Find().IsAvailableForFrontend().IsSmallMediaBanner()
или выражайте запросы в виде лямбд с моими спецификациями
(Лямбда): bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)
или, может быть, каким-то совершенно другим способом?Самое главное, что парень, реализующий MVC front, должен иметь хороший интуитивный опыт работы с репозиторием.
Чего я надеюсь достичь, так это сохранить гибкость som в отношении возможности комбинировать спецификации и предоставить возможность "фильтрации" с помощью спецификаций, но без передачи IQueryable контроллеру, а скорее как ISpecifiable, который позволяет изменять запрос только с помощью спецификаций, а не с помощью Linq.Но могу ли я просто вернуться к утечке логики запроса в контроллер таким образом?
Решение
Я видел несколько Fluent API, которые используют Свойства для спецификаций, поэтому они не добавляют шум в скобках клиентам.
bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()
Будучи 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;
}
}
Итак, у меня есть объект, его интерфейс спецификации и несколько разработчиков, участвующих в шаблоне visitor, его интерфейс репозитория, принимающий интерфейс спецификации, и его реализация репозитория, принимающая посетителя, способного переводить спецификации в предложения SQL (но это всего лишь вопрос это случай, конечно).Наконец, я бы составил спецификацию "вне" интерфейса репозитория (используя fluent interface).
Может быть, это просто наивная идея, но я нахожу ее довольно простой.Надеюсь, это поможет.
Лично я бы выбрал лямбда-способ.Возможно, это из-за моей любви к 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);
}
}
Теперь мы можем создать интерфейс для таблицы / объектов banners, который реализует наш 'IRepository' и конкретный класс, расширяющий абстрактный класс 'Repository' и реализующий 'IBannerInterface':
interface IBannerRepository : IRepository<Banner>
{
}
И соответствующий репозиторий для его реализации:
class BannerRepository : Repository<Banner>, IBannerRepository
{
}
Я бы предложил использовать этот подход, поскольку он дает вам большую гибкость, а также достаточную мощность для управления всеми имеющимися у вас крошечными объектами.
Таким образом, вызвать эти методы будет очень просто:
BannerRepository _repo = new BannerRepository();
_repo.FindSingle(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner);
Да, это означает, что вам нужно выполнить некоторую работу, но позже вам будет чертовски проще изменить источник данных.
Надеюсь, это поможет!