Repositorio de Métodos vs Extender IQueryable


Tengo repositorios (por ejemplo, ContactRepository, UserRepository, etc.) que encapsulan el acceso de datos al modelo de dominio.

Cuando estaba mirando buscando datos , por ejemplo,

  • encontrar un contacto cuyo nombre comienza con XYZ
  • Un contacto cuyo cumpleaños es después 1960

    (etc),

Comencé a implementar métodos de repositorio como FirstNameStartsWith (string prefix) y YoungerThanBirthYear(int year), básicamente siguiendo los muchos ejemplos que hay.

Entonces tengo un problema - ¿qué pasa si tengo que combinar varias búsquedas? Cada uno de mis métodos de búsqueda de repositorio, como el anterior, solo devuelve un conjunto finito de objetos de dominio reales. En busca de una mejor manera, empecé a escribir métodos de extensión en IQueryable, por ejemplo, esto:

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, me encontré escribiendo métodos de extensión (e inventando clases locas como ContactsQueryableExtensions por todas partes, y pierdo el "buen agrupamiento" al tener todo en el repositorio apropiado.

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

Author: svick, 2009-09-13

4 answers

@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.

Luego, 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, ejemplo de tiempo.

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 en él llamado Contacts .. que es básicamente su clase sql Contacts.

Esto significa que el método básicamente está haciendo: SELECT * FROM CONTACTS ... pero no está golpeando la base de datos con esa consulta .. es sólo una consulta en este momento.

Ok .. siguiente capa.. PATADA ... up we go (Inception ¿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();
}

Hecho.

Así que recapitulemos. Primero, comenzamos con una simple consulta' Get everything from contacts'. Ahora, si tenemos un nombre proporcionado, vamos a añadir un filtro para filtrar todos los contactos por nombre. A continuación, si tenemos un año proporcionado, filtramos el cumpleaños por año. Sucesivamente. Finalmente, entonces golpeamos la base de datos (con esta consulta modificada)y vemos qué resultados obtenemos.

NOTAS: -

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

Comida para llevar puntos

  • La capa de servicios maneja todas las inteligencias. Ahí es donde usted decide qué datos necesita.
  • El repositorio es un simple SELECT * DE LA TABLA o un simple INSERT/UPDATE en la TABLA.

Buena suerte:)

 6
Author: Pure.Krome,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-02 01:25:18

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

Siento que el patrón de repo 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, tal vez, formas de repensar por qué está pidiendo los datos de tantas maneras? Si no, realmente siento que un enfoque híbrido es la mejor manera de hacerlo. Crea métodos de repo para las cosas que reutilizas. Cosas para las que realmente tiene sentido. SECO y todo eso. Pero los one-offs? ¿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. SECO no se aplica realmente allí ¿verdad?

Tomaría disciplina para hacer esto bien, pero realmente creo que es un camino apropiado.

 12
Author: Chad Ruppert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-13 01:20:43

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

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

Ordenar consultas

Si el método se usa solo para ordenar, prefiero escribir métodos de extensión que operan en IQueryable<T> o IOrderedQueryable<T> (para aprovechar el subyacente proveedor.) 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() según sea necesario dentro de mi clase de repositorio.

Consultas que devuelven instancias individuales

Si el método implica consultar por parámetros primitivos, generalmente requiere un ObjectContext y no se puede hacer fácilmente static. Estos métodos los 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 en su lugar implica consultar un EntityObject usando sus propiedades de navegación , generalmente se puede hacer static muy fácilmente, e implementado 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 convenientemente someStudent.GetLatestRegistration() sin necesidad de una instancia de repositorio en el ámbito actual.

Consultas que devuelven colecciones

Si el método devuelve algunos IEnumerable, ICollection o IList, entonces me gusta hacerlo static si es posible, y dejarlo en el repositorio incluso si usa 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 porque mis métodos GetAll() ya está en vivo en el repositorio, y ayuda a evitar un desorden desordenado de métodos de extensión.

Otra razón para no implementar estos "getters de colección" como métodos de extensión es que requerirían nombres más detallados para ser significativos, ya que el tipo devuelto no está implícito. Por ejemplo, el último ejemplo sería GetTermRegistrationsByTerm(this Term term).

Espero que esto ayude!

 4
Author: Rob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-15 17:42:28

Seis años más tarde, 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.

El papel principal de una capa de servicio es adherirse al principio separación de preocupaciones y abordar lógica de comandos asociado con la función de negocio.

En aplicaciones del mundo real, la lógica de consulta a menudo no necesita extensión más allá de la mecánica de recuperación proporcionada por el propio repositorio (ej. 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 diferentes roles. El primero es un filtro simple, mientras que el segundo implica necesidad de negocio (ej. mantenimiento o facturación). En un simple aplicación uno podría implementar ambos 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 CQRS la estrategia de separar comandos y consultas.

Consideraciones principales son: (a) el propósito principal de su repositorio y (b) ¿cuánto le gusta a adherirse a CQRS y DDD filosofías.

 0
Author: one.beat.consumer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-10 19:44:38