Pergunta

Atualmente estou a criação de um novo projeto, e eu ter executado em algumas coisas, onde eu precisam de um pouco de entrada.

Isto é o que eu estou pensando em:

  • Eu gostaria de um repositório genérico

  • Eu não quero retornar IQueryable do meu repositório.

  • Gostaria de encapsular minhas consultas nas especificações.

  • Eu tenho implementado o padrão de especificação

  • Ele precisa ser facilmente testável

Agora, este é o lugar onde eu fico um pouco preso e minha pergunta é qual o caminho seria a maneira mais elegante de chamar o método find com um ou mais especificações:

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

ou expressar consultas como lambdas com minhas especificações

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

ou talvez alguns completamente outra maneira? A coisa mais importante é que o cara implementação da frente MVC, deve ter uma boa experiência intuitiva do repositório.

O que eu estou esperando para conseguir se manter flexibilidade som com relação a ser capaz de combinar especificações, e dar a experiência de "filtragem" com as Specfications, mas sem escapar um IQueryable para o controlador, mas mais como um ISpecifiable, que só permite modificar a consulta com as especificações e não com Linq. Mas eu sou só volta ao vazamento lógica de consulta para o controlador dessa maneira?

Foi útil?

Solução

Eu vi algumas API Fluent é que usa propriedades para obter as especificações, para que eles não adicionar o ruído parêntese para os clientes.

bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()

Sendo Exec () um método para a execução das especificações contra o repo.

Mas mesmo se você não usar as propriedades, gostaria de ir para a API fluente, uma vez que tem o mínimo de ruído.

Outras dicas

ou talvez alguns completamente outra maneira?

Bem, na verdade eu não entendo exatamente sua implementação repositório (por exemplo, qual será o retorno do método .Find()?), Mas eu escolheria outra direção:

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

Então, eu tenho uma entidade, a sua interface especificação e vários implementadores envolvidos em um padrão do visitante, sua interface de repositório aceitar uma interface especificação e sua implementação repositório, aceitar um visitante capaz de traduzir as especificações em cláusulas SQL (mas é apenas uma questão de este caso, é claro). Finalmente, gostaria de compor especificação "fora" da interface de repositório (usando interface fluente).

Talvez esta é apenas uma idéia ingênua, mas acho que é bastante simples. Espero que isso ajude.

Pessoalmente, eu iria com a forma lambda. Pode ser por causa do meu amor por lambda mas fornece lote de espaço para uma configuração de repositório genérico.

Considerando o seguinte:

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

Eu não sei o que o seu teste padrão olha como, mas você poderia refatorar algumas coisas aqui:

Criar uma interface genérica chamada 'IRepository' do tipo que contém todos os métodos de acesso de dados.

Ele poderia ficar assim:

interface IRepository<T> where T : class
{
    IEnumerable<T> FindAll(Func<T, bool> exp);

    T FindSingle(Func<T, bool> exp);
}   

Criar uma classe abstrata 'repositório' implementar essa interface:

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

Agora podemos criar uma interface para a tabela de banners / objetos que implementa o nosso 'IRepository' e uma classe concreta estendendo a classe abstrata 'Repository' e implementar o 'IBannerInterface':

interface IBannerRepository : IRepository<Banner>
{
}

E o repositório de correspondência para implementá-lo:

class BannerRepository : Repository<Banner>, IBannerRepository
{
}

Gostaria de sugerir usando esta abordagem, uma vez que lhe dá uma grande flexibilidade, bem como poder suficiente para controlar todas as pequenas entidades que você tem.

Chamar esses métodos vai ser super fácil dessa maneira:

BannerRepository _repo = new BannerRepository();

_repo.FindSingle(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner);

Sim, isso significa que você tem que fazer algum trabalho, mas é o inferno mais fácil para você mudar a fonte de dados mais tarde.

Espero que ajude!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top