Pregunta

Tengo los repositorios (por ejemplo,ContactRepository, UserRepository y así sucesivamente) que encapsulan el acceso a los datos el modelo de dominio.

Cuando yo estaba buscando en la búsqueda de datos, por ejemplo,

  • búsqueda de un contacto cuyo nombre de pila comienza con XYZ
  • un contacto cuyo cumpleaños es después de 1960

    (etc),

Me empezó a aplicar el repositorio de la aplicación de métodos tales como FirstNameStartsWith(string prefijo) y YoungerThanBirthYear(int año), básicamente siguiendo los muchos ejemplos que hay.

Entonces llegué a un problema - ¿qué pasa si tengo que combinar múltiples búsquedas?Cada uno de mis repositorio de métodos de búsqueda, tales como el anterior, sólo devolverá un conjunto finito de dominio real de los objetos.En la búsqueda de una mejor manera, empecé a escribir los métodos de extensión en IQueryable<T>, por ejemplo,este:

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

Ahora puedo hacer cosas como

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

Sin embargo, he encontrado a mí mismo escribiendo los métodos de extensión (y la invención de los locos de las clases, tales como ContactsQueryableExtensions todo, y voy a perder la agrupación" por tener todo en el correspondiente repositorio.

Es esta realmente la manera de hacerlo, o hay una mejor manera de lograr el mismo objetivo?

¿Fue útil?

Solución

@Alex - Sé que esta es una vieja pregunta, pero lo que estaría haciendo sería dejar que el Repositorio haga cosas realmente simples solamente. Esto significa, obtener todos los registros de una tabla o vista.

Entonces, en la capa de SERVICIOS (está utilizando una solución de n niveles, ¿verdad? :)) estaría manejando todas las cosas de consulta 'especiales' allí.

Ok, hora de ejemplo.

Capa de repositorio

ContactRepository.cs

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

Agradable y simple. SqlContext es la instancia de su EF Context .. que tiene un Entity llamado Contacts .. que es básicamente su clase de Contactos sql.

Esto significa que ese método básicamente está haciendo: SELECT * FROM CONTACTS ... pero no está afectando la base de datos con esa consulta ... es solo una consulta en este momento.

Ok .. siguiente capa .. KICK ... arriba vamos ( Inicio ¿alguien?)

Capa de servicios

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

Listo.

Así que recapitulemos. Primero, comenzamos con una simple consulta ' Obtener todo de los contactos '. Ahora, si tenemos un nombre proporcionado, agreguemos un filtro para filtrar todos los contactos por nombre. A continuación, si tenemos un año, filtramos el cumpleaños por año. Etc. Finalmente, llegamos a la base de datos (con esta consulta modificada) y vemos qué resultados obtenemos.

NOTAS: -

  • He omitido cualquier inyección de dependencia por simplicidad. Es más que muy recomendable.
  • Esto es todo pseduo-code. No probado (contra un compilador) pero tienes la idea ...

Puntos de comida para llevar

  • La capa de Servicios maneja todos los conocimientos. Ahí es donde usted decide qué datos necesita.
  • El repositorio es un simple SELECT * FROM TABLE o un simple INSERT / UPDATE en TABLE.

Buena suerte :)

Otros consejos

He estado pensando mucho en esto últimamente, después de comenzar en mi trabajo actual. Estoy acostumbrado a los repositorios, van por el camino completo de IQueryable utilizando solo repositorios de huesos desnudos como usted sugiere.

Siento que el patrón de repositorio es sólido y hace un trabajo semi-efectivo al describir cómo desea trabajar con los datos en el dominio de la aplicación. Sin embargo, el problema que está describiendo definitivamente ocurre. Se vuelve desordenado, rápido, más allá de una simple aplicación.

¿Hay, quizás, formas de repensar por qué está solicitando los datos de tantas maneras? Si no, realmente siento que un enfoque híbrido es el mejor camino a seguir. Cree métodos de repositorio para las cosas que reutiliza. Cosas para las que tiene sentido. SECO y todo eso. ¿Pero esos únicos? ¿Por qué no aprovechar IQueryable y las cosas sexys que puedes hacer con él? Es tonto, como dijiste, crear un método para eso, pero no significa que no necesites los datos. DRY realmente no se aplica allí, ¿verdad?

Se necesitaría disciplina para hacerlo bien, pero realmente creo que es un camino apropiado.

Me doy cuenta de que esto es viejo, pero he estado lidiando con este mismo tema últimamente, y llegué a la misma conclusión que Chad:con un poco de disciplina, un híbrido de los métodos de extensión y repositorio de métodos parece funcionar mejor.

Algunas reglas generales que he estado siguiendo en mi (Entity Framework) de la aplicación:

Consultas sobre pedidos

Si el método sólo se utiliza para ordenar, yo prefiero escribir los métodos de extensión que operan en IQueryable<T> o IOrderedQueryable<T> (para aprovechar el proveedor subyacente.) por ejemplo,

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

Ahora puedo usar ThenByStudentName() como sea necesario dentro de mi repositorio de clase.

Consultas que devuelven los casos singulares

Si el método consiste en consultar por primitivo parámetros, por lo general requiere un ObjectContext y no puede ser fácilmente static.Estos métodos dejo en mi repositorio, por ejemplo,

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

Sin embargo, si el método implica la consulta de un EntityObject el uso de su propiedades de navegación, puede ser realizado static con bastante facilidad, y se implementó como un método de extensión. por ejemplo,

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

Ahora puedo escribir muy bien someStudent.GetLatestRegistration() sin necesidad de un repositorio de ejemplo, en el ámbito actual.

Consultas que devuelven colecciones

Si el método devuelve IEnumerable, ICollection o IList, entonces me gusta hacer static si es posible, y dejar en el repositorio incluso si utiliza las propiedades de navegación. por ejemplo,

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

Esto es debido a mi GetAll() los métodos que ya viven en el repositorio, y ayuda a evitar un completo desorden de los métodos de extensión.

Otra razón para no implementar estos "colección de los captadores" como la extensión de los métodos es que requieren más detallado de nomenclatura para ser significativo, ya que el tipo de retorno no es implícita.Por ejemplo, el último ejemplo sería GetTermRegistrationsByTerm(this Term term).

Espero que esto ayude!

Seis años después, estoy seguro de que @Alex ha resuelto su problema, pero después de leer la respuesta aceptada, quería agregar mis dos centavos.

El propósito general de extender IQueryable colecciones en un repositorio para proporcionar flexibilidad y capacitar a sus consumidores para personalizar la recuperación de datos. Lo que Alex ya ha hecho es un buen trabajo.

La función principal de una capa de servicio es adherirse al principio de separación de preocupaciones y abordar la lógica de comando asociada con la función comercial.

En aplicaciones del mundo real, lógica de consulta a menudo no necesita extensión más allá de la mecánica de recuperación proporcionada por el repositorio en sí (por ejemplo, alteraciones de valor, conversiones de tipo).

Considere los dos escenarios siguientes:

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 métodos son extensiones de consulta simples, pero por sutiles que sean, tienen roles diferentes. El primero es un filtro simple, mientras que el segundo implica una necesidad comercial (por ejemplo, mantenimiento o facturación). En una aplicación simple, uno podría implementarlos en un repositorio. En un sistema más idealista, UsedThisYear es el más adecuado para la capa de servicio (e incluso puede implementarse como un método de instancia normal) donde también puede facilitar mejor la estrategia CQRS de separar los comandos y consultas .

Las consideraciones clave son (a) el propósito principal de su repositorio y (b) cuánto le gusta adherirse a las filosofías de CQRS y DDD .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top