Question

J'ai des référentiels (par exemple, ContactRepository, UserRepository, etc.) qui encapsulent l'accès aux données du modèle de domaine.

Lorsque je cherchais à rechercher des données , par exemple.

  • recherche d'un contact dont le prénom commence par XYZ
  • un contact dont l'anniversaire est après 1960

    (etc),

J'ai commencé à implémenter des méthodes de référentiel telles que FirstNameStartsWith (préfixe de chaîne) et YoungerThanBirthYear (année int) , en suivant essentiellement les nombreux exemples proposés.

Ensuite, je rencontre un problème - que se passe-t-il si je dois combiner plusieurs recherches? Chacune de mes méthodes de recherche dans le référentiel, telle que celle décrite ci-dessus, ne renvoie qu'un ensemble fini d'objets de domaine réels. À la recherche d'un meilleur moyen, j'ai commencé à écrire des méthodes d'extension sur IQueryable & Lt; T & Gt ;, par exemple. ceci:

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

Je peux maintenant faire des choses comme

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

Cependant, je me suis retrouvé à écrire des méthodes d'extension (et à inventer des classes folles telles que ContactsQueryableExtensions ), et je perds le & "joli groupement &" en ayant tout dans le référentiel approprié.

Est-ce vraiment la manière de procéder ou existe-t-il une meilleure façon d'atteindre le même objectif?

Était-ce utile?

La solution

@Alex - Je sais que c'est une vieille question, mais ce que je ferais serait de laisser le référentiel faire des choses très simples uniquement. Cela signifie obtenir tous les enregistrements d'une table ou d'une vue.

Ensuite, dans la couche SERVICES (vous utilisez une solution à plusieurs niveaux, n'est-ce pas? :)), je gérerais tout le contenu de la requête "spéciale".

Ok, exemple de temps.

Couche de référentiel

ContactRepository.cs

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

Nice et simple. SqlContext est l'instance de votre EF Context .. qui comporte un Entity appelé Contacts .. qui est essentiellement votre classe de contacts SQL.

Cela signifie que cette méthode consiste essentiellement à: SELECT * FROM CONTACTS ... mais elle ne frappe pas la base de données avec cette requête ... ce n'est qu'une requête pour le moment.

Ok. Couche suivante .. KICK ... c'est parti ( À la création , ça vous tente?)

Couche de services

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

Terminé.

Alors récapitulons. Premièrement, nous commençons notre avec une requête ' Tout obtenir des contacts '. Maintenant, si nous avons un nom fourni, ajoutons un filtre pour filtrer tous les contacts par nom. Ensuite, si nous avons une année fournie, nous filtrons les anniversaires par année. Etc. Enfin, nous tapons ensuite sur la base de données (avec cette requête modifiée) et voyons les résultats obtenus.

NOTES: -

  • J'ai omis toute injection de dépendance pour plus de simplicité. C'est plus que hautement recommandé.
  • Ceci est tout pseduo-code. Non testé (contre un compilateur) mais vous avez l'idée ....

Points à emporter

  • La couche Services gère tous les intelligences. C’est là que vous décidez des données dont vous avez besoin.
  • Le référentiel est un simple SELECT * FROM TABLE ou un simple INSERT / UPDATE dans TABLE.

Bonne chance:)

Autres conseils

J'y ai beaucoup réfléchi ces derniers temps, après avoir commencé à occuper mon poste actuel. Je suis habitué aux référentiels, ils empruntent le chemin complet d'IQueryable en utilisant uniquement des référentiels sans structure, comme vous le suggérez.

Je pense que le modèle de prise en pension est correct et fait un travail semi-efficace en décrivant comment vous souhaitez utiliser les données du domaine d'application. Cependant, le problème que vous décrivez se produit définitivement. Cela devient désordonné, rapide, au-delà d’une simple application.

Existe-t-il, peut-être, des façons de repenser pourquoi vous demandez les données de tant de façons? Sinon, j'estime vraiment qu'une approche hybride est la meilleure solution. Créez des méthodes de repo pour les éléments que vous réutilisez. Des choses qui ont du sens pour. SEC et tout ça. Mais ces one-offs? Pourquoi ne pas profiter d'IQueryable et des choses sexy que vous pouvez faire avec? Comme vous l'avez dit, il est idiot de créer une méthode pour cela, mais cela ne signifie pas que vous n'avez pas besoin des données. DRY ne s'applique pas vraiment là-bas?

Il faudrait de la discipline pour bien faire cela, mais je pense vraiment que c'est un chemin approprié.

Je réalise que c'est vieux, mais je m'occupe du même problème récemment et je suis arrivé à la même conclusion que Chad: avec un peu de discipline, un hybride de méthodes d'extension et de méthodes de stockage semble mieux fonctionner.

Certaines règles générales que j'ai suivies dans mon application (Entity Framework):

Commande de requêtes

Si la méthode est utilisée uniquement pour la commande, je préfère écrire des méthodes d'extension fonctionnant sur IQueryable<T> ou IOrderedQueryable<T> (pour tirer parti du fournisseur sous-jacent.) Par exemple.

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

Je peux maintenant utiliser ThenByStudentName() selon les besoins de ma classe de référentiel.

Requêtes renvoyant des instances uniques

Si la méthode implique une interrogation à l'aide de paramètres primitifs, elle nécessite généralement un ObjectContext et ne peut pas être effectuée facilement static. Ces méthodes que je laisse sur mon référentiel, par exemple

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

Toutefois, si la méthode implique plutôt d'interroger un EntityObject à l'aide de ses propriétés de navigation , cela peut généralement être fait someStudent.GetLatestRegistration() assez facilement et implémenté comme méthode d'extension. par exemple

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

Maintenant, je peux facilement écrire IEnumerable sans avoir besoin d'une instance de référentiel dans l'étendue actuelle.

Requêtes renvoyant des collections

Si la méthode retourne des éléments ICollection, IList ou GetAll(), j'aime bien le rendre GetTermRegistrationsByTerm(this Term term) si possible et le laisser dans le référentiel même s'il utilise les propriétés de navigation. par exemple

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

En effet, mes <=> méthodes résident déjà dans le référentiel et permettent d'éviter un fouillis encombré de méthodes d'extension.

Une autre raison de ne pas implémenter ces & "; getters de collection &"; En tant que méthodes d'extension, elles nécessitent une dénomination plus détaillée, car le type de retour n'est pas impliqué. Par exemple, le dernier exemple deviendrait <=>.

J'espère que cela aide!

Six ans plus tard, je suis certain que @Alex a résolu son problème, mais après avoir lu la réponse acceptée, je voulais ajouter mes deux sous.

L'objectif général consistant à étendre IQueryable des collections dans un référentiel afin de fournir une flexibilité et de permettre à ses clients de personnaliser la récupération des données. Ce que Alex a déjà fait est du bon travail.

Le rôle principal d’une couche de service consiste à adhérer au principe de séparation des préoccupations et à adresser la logique de commande associée à la fonction métier.

Dans les applications du monde réel, la logique de requête ne nécessite souvent aucune extension, hormis les mécanismes de récupération fournis par le référentiel lui-même (par exemple, les modifications de valeur, les conversions de types).

Considérez les deux scénarios suivants:

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

Les deux méthodes sont de simples extensions de requête, mais elles ont des rôles différents, même les plus subtiles. Le premier est un filtre simple, tandis que le second implique un besoin commercial (par exemple, maintenance ou facturation). Dans une application simple, il est possible de les implémenter dans un référentiel. Dans un système plus idéaliste, UsedThisYear convient mieux à la couche service (et peut même être implémenté en tant que méthode d'instance normale), ce qui facilitera également la CQRS stratégie de séparation des commandes et requêtes .

Les principales considérations sont (a) l'objectif principal de votre référentiel et (b) dans quelle mesure aimez-vous adhérer aux philosophies CQRS et DDD .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top