Шаблон репозитория и спецификации

в настоящее время я создаю новый проект, и я столкнулся с несколькими вещами, где мне нужен небольшой вклад.

вот что я думаю:

  • Я хотел бы общий репозиторий

  • Я не хочу возвращать 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);

Да, это означает, что вы должны сделать некоторую работу, но это легче, ад для вас, чтобы изменить источник данных позже.

надеюсь, что это помогает!