Pergunta

I dispõe de depósitos (por exemplo ContactRepository, UserRepository e assim por diante) que o acesso aos dados encapsulado para o modelo de domínio.

Quando eu estava olhando para em busca de dados , por exemplo,

  • encontrar um contacto cujo primeiro nome começa com XYZ
  • um contato cujo aniversário é depois 1960

    (etc),

I começou a implementar métodos de repositório, como FirstNameStartsWith (prefixo string) e YoungerThanBirthYear (ano int) , basicamente, seguindo os muitos exemplos lá fora.

Então eu bati um problema - o que se eu tenho que combinar várias pesquisas? Cada um dos meus métodos de pesquisa do repositório, como acima, apenas retornar um conjunto finito de objetos de domínio reais. Em busca de uma maneira melhor, comecei a escrever métodos extensão em IQueryable , por exemplo, isto:

public static IQueryable<Contact> FirstNameStartsWith(
               this IQueryable<Contact> contacts, String prefix)
{
    return contacts.Where(
        contact => contact.FirstName.StartsWith(prefix));
}        

Agora eu posso fazer coisas como

ContactRepository.GetAll().FirstNameStartsWith("tex").YoungerThanBirthYear(1960);

No entanto, eu encontrei-me escrever métodos de extensão (e inventar aulas loucas, como ContactsQueryableExtensions todo, e eu perco o "agrupamento agradável" por ter tudo no repositório adequado.

Esta é realmente a maneira de fazê-lo, ou se existe uma maneira melhor para alcançar o mesmo objetivo?

Foi útil?

Solução

@Alex - eu sei que isso é uma questão de idade, mas o que eu estaria fazendo seria deixar o do Repositório coisas realmente simples somente. Isto significa obter todos os registros para uma tabela ou exibição.

Em seguida, na camada de serviços (você estiver usando uma solução de n-camadas, certo? :)) i seria lidar com todas as coisas 'especial' consulta lá.

Ok, exemplo tempo.

Camada de Repositório

ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}

Nice e simples. SqlContext é a instância de sua EF Context .. que tem uma Entity nele chamado Contacts .. que é basicamente sua classe Contactos sql.

Isto significa que, método basicamente está fazendo:. SELECT * FROM CONTACTS ... mas não é bater o banco de dados com essa consulta .. é apenas uma consulta agora

Ok .. próxima camada .. KICK ... se nós vamos ( A Origem alguém?)

Serviços Camada

ContactService.cs

public  ICollection<Contact> FindContacts(string name)
{
    return FindContacts(name, null)
}

public ICollection<Contact> FindContacts(string name, int? year)
{
   IQueryable<Contact> query = _contactRepository.GetContacts();

   if (!string.IsNullOrEmpty(name))
   {
       query = from q in query
               where q.FirstName.StartsWith(name)
               select q;
   }

   if (int.HasValue)
   {
       query = from q in query
               where q.Birthday.Year <= year.Value
               select q);
    }

    return (from q in query
            select q).ToList();
}

Feito.

Então vamos recapitular. Primeiro, nós começamos nosso com um simples ' Obter tudo, desde contatos ' consulta. Agora, se temos um nome fornecido, vamos adicionar um filtro para filtrar todos os contatos por nome. Em seguida, se temos um ano desde que, em seguida, nós filtrar o aniversário por ano. Etc. Finalmente, em seguida, bateu o DB (com esta consulta modificado) e ver o que resulta voltarmos.

NOTAS: -

  • omiti qualquer Dependency Injection para simplificar. É mais do que altamente recomendado.
  • Isto é tudo pseduo-código. Não testado (contra um compilador), mas você começa a idéia ....

Takeaway aponta
  • Os serviços da camada de puxadores todos os smarts. Isso é onde você decidir quais os dados que você precisa.
  • O Repositório é um SELECT * FROM TABLE simples ou de um forro simples / UPDATE na tabela.

Boa sorte:)

Outras dicas

Eu tenho pensado muito sobre isso ultimamente, depois de começar no meu trabalho atual. Estou habituado a Repositories, eles vão a caminho IQueryable completo usando ossos apenas descalços repositórios como você sugere.

Eu sinto o padrão de repo é boa e faz um trabalho semi-effective descrever como você deseja trabalhar com os dados no domínio da aplicação. No entanto, o problema que você está descrevendo definitivamente ocorre. Ele fica confuso, rápido, além de uma aplicação simples.

Existem, talvez, formas de repensar por que você está pedindo para os dados de tantas maneiras? Se não, eu realmente sinto que uma abordagem híbrida é o melhor caminho a percorrer. Criar métodos de reporte para as coisas que você reutilizar. Coisas que realmente faz sentido para. DRY e tudo isso. Mas esses one-offs? Por que não aproveitar de IQueryable e as coisas sensuais que você pode fazer com ele? É bobagem, como você disse, para criar um método para isso, mas isso não significa que você não precisa os dados. DRY não se aplica realmente não faz isso?

Seria preciso disciplina para fazer isso bem, mas eu realmente acho que é um caminho adequado.

Sei que isso é velho, mas eu tenho lidado com este mesmo problema ultimamente, e eu chegou à mesma conclusão que o Chade: com um pouco de disciplina, um híbrido de métodos de extensão e métodos de repositório parece funcionar melhor <. / p>

Algumas regras gerais Eu tenho acompanhado no meu aplicativo (Entity Framework):

Ordenação consultas

Se o método é usado apenas para a encomenda, eu prefiro os métodos de extensão de escrita que operam em IQueryable<T> ou IOrderedQueryable<T> (para alavancar o provedor subjacente.) por exemplo.

public static IOrderedQueryable<TermRegistration> ThenByStudentName(
    this IOrderedQueryable<TermRegistration> query)
{
    return query
        .ThenBy(reg => reg.Student.FamilyName)
        .ThenBy(reg => reg.Student.GivenName);
}

Agora eu posso usar ThenByStudentName() conforme necessário dentro de minha classe repositório.

Consultas retornar instâncias individuais

Se o método envolve a consulta por parâmetros primitivos, que normalmente requer uma ObjectContext e não pode ser feita facilmente static. Estes métodos deixo no meu repositório, por exemplo.

public Student GetById(int id)
{
    // Calls context.ObjectSet<T>().SingleOrDefault(predicate) 
    // on my generic EntityRepository<T> class 
    return SingleOrDefault(student => student.Active && student.Id == id);
}

No entanto, se o método em vez envolve consultando uma EntityObject usando seus propriedades de navegação , que geralmente podem ser feitas static com bastante facilidade, e implementado como um método de extensão. por exemplo.

public static TermRegistration GetLatestRegistration(this Student student)
{
    return student.TermRegistrations.AsQueryable()
        .OrderByTerm()
        .FirstOrDefault();
}

Agora posso someStudent.GetLatestRegistration() convenientemente gravação sem a necessidade de uma instância de repositório no escopo atual.

Consultas coleções retornando

Se o método retorna alguns IEnumerable, ICollection ou IList, então eu gostaria de torná-lo static se possível, e deixá-lo no repositório mesmo que usa propriedades de navegação. ex

public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
    var termReg = term.TermRegistrations;
    return (ordered)
        ? termReg.AsQueryable().OrderByStudentName().ToList()
        : termReg.ToList();
}

Isto é porque os meus métodos GetAll() já vivo no repositório, e que ajuda a evitar uma bagunça desordenada de métodos de extensão.

Outra razão para não implementar estes "getters coleção" como métodos de extensão é que eles exigiria mais detalhado nomear para ser significativo, uma vez que o tipo de retorno não está implícita. Por exemplo, o último exemplo seria GetTermRegistrationsByTerm(this Term term).

Espero que isso ajude!

Seis anos mais tarde, estou certo @Alex tenha resolvido o seu problema, mas depois de ler a resposta aceita eu queria acrescentar meus dois centavos.

O objetivo geral das coleções IQueryable que se estende em um repositório para fornecer flexibilidade e capacitar seus consumidores personalizar recuperação de dados. O que Alex já fez um bom trabalho.

O principal papel de um camada de serviço é a aderir ao separação de interesses princípio e endereço lógica de comando associado com a função de negócios.

Em aplicações do mundo real, lógica de consulta muitas vezes precisa de nenhuma extensão além da mecânica de recuperação fornecidas pelo próprio repositório (ex. Alterações de valor, tipo de conversões).

Considere os dois cenários seguintes:

IQueryable<Vehicle> Vehicles { get; }

// raw data
public static IQueryable<Vehicle> OwnedBy(this IQueryable<Vehicle> query, int ownerId)
{
    return query.Where(v => v.OwnerId == ownerId);
}

// business purpose
public static IQueryable<Vehicle> UsedThisYear(this IQueryable<Vehicle> query)
{
    return query.Where(v => v.LastUsed.Year == DateTime.Now.Year);
}

Ambos os métodos são extensões de consulta simples, mas por mais sutil que têm papéis diferentes. O primeiro é um filtro simples, enquanto a segunda implica necessidade do negócio (ex. Manutenção ou faturamento). Em um aplicativo simples pode implementá-los tanto em um repositório. Em um sistema mais idealista UsedThisYear é mais adequado para a camada de serviço (e pode até ser implementado como um método de instância normal) onde também pode facilitar uma melhor CQRS estratégia de separar comandos e consultas .

As principais considerações são: (a) o objetivo principal de seu repositório e (b) o quanto você gosta de aderir a CQRS e DDD filosofias.

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