2012-10-10 1 views
3

Assumer le Poços simple suivant, Pays et État:POCO de référentiel à niveaux multiples - Agrégats?

public partial class Country 
{ 
    public Country() 
    { 
     States = new List<State>(); 
    } 
    public virtual int CountryId { get; set; } 
    public virtual string Name { get; set; } 
    public virtual string CountryCode { get; set; } 
    public virtual ICollection<State> States { get; set; } 
} 

public partial class State 
{ 
    public virtual int StateId { get; set; } 
    public virtual int CountryId { get; set; } 
    public virtual Country Country { get; set; } 
    public virtual string Name { get; set; } 
    public virtual string Abbreviation { get; set; } 
} 

Supposons maintenant je simple respository qui ressemble à ceci:

public partial class CountryRepository : IDisposable 
{ 
    protected internal IDatabase _db; 

    public CountryRepository() 
    { 
     _db = new Database(System.Configuration.ConfigurationManager.AppSettings["DbConnName"]); 
    } 

    public IEnumerable<Country> GetAll() 
    { 
     return _db.Query<Country>("SELECT * FROM Countries ORDER BY Name", null); 
    } 

    public Country Get(object id) 
    { 
     return _db.SingleById(id); 
    } 

    public void Add(Country c) 
    { 
     _db.Insert(c); 
    } 

    /* ...And So On... */ 
} 

Généralement dans mon interface utilisateur, je n'affiche pas tous les enfants (états), mais je montre un compte agrégé. Donc, mon modèle de vue sur la liste des pays pourrait ressembler à ceci:

public partial class CountryListVM 
{ 
    [Key] 
    public int CountryId { get; set; } 
    public string Name { get; set; } 
    public string CountryCode { get; set; } 
    public int StateCount { get; set; } 
} 

Lorsque j'utilise le fournisseur de données sous-jacentes (Entity Framework, NHibernate, PetaPoco, etc.) directement dans ma couche d'interface utilisateur, je peux facilement faire quelque chose comme ça :

IList<CountryListVM> list = db.Countries 
    .OrderBy(c => c.Name) 
    .Select(c => new CountryListVM() { 
     CountryId = c.CountryId, 
     Name = c.Name, 
     CountryCode = c.CountryCode, 
     StateCount = c.States.Count 
    }) 
    .ToList(); 

Mais lorsque j'utilise un référentiel ou un modèle de service, je supprime l'accès direct à la couche de données. Il semble que mes options sont:

  1. Retour le pays avec une collection États peuplés, carte puis sur la couche d'interface utilisateur. L'inconvénient de cette approche est que je retourne beaucoup plus de données que nécessaire.

    -ou-

  2. Mettez tous mes modèles de vue dans ma bibliothèque dll commune (par opposition à les avoir dans le répertoire des modèles dans mon application MVC) et d'élargir mon répertoire pour retourner des modèles de vue spécifiques au lieu de simplement la pocos de domaine. L'inconvénient de cette approche est que je fuis des éléments spécifiques à l'interface utilisateur (annotations de validation de données MVC) dans mes POCO précédemment nettoyés.

    -ou-

  3. Y at-il d'autres options?

Comment gérez-vous ce genre de choses?

Répondre

1

Honnêtement, votre question m'a donné matière à réflexion pendant quelques jours. De plus en plus, j'ai tendance à penser que la dénormalisation est la bonne solution. Regardez, le point principal de la conception pilotée par domaine est de laisser le domaine du problème piloter vos décisions de modélisation. Considérez l'entité du pays dans le monde réel. Un pays a une liste d'états. Cependant, quand vous voulez connaître le nombre d'états d'un pays donné, vous n'allez pas sur la liste des états de l'encyclopédie et ne les comptez pas. Vous êtes plus susceptible de regarder les statistiques du pays et de vérifier le nombre d'états là-bas. À mon humble avis, le même comportement devrait être reflété dans votre modèle de domaine. Vous pouvez avoir cette information dans la propriété du pays, ou introduire un type d'objet CountryStatistics. Quelle que soit l'approche que vous choisissez, elle doit faire partie de l'agrégat du pays. Être dans la limite de cohérence de l'agrégat garantira qu'il contient des données cohérentes en cas d'ajout ou de suppression d'un état.

1

D'autres approches:

  • Si la collection états ne devrait pas changer beaucoup, vous pouvez permettre un peu de dénormalisation - propriété ajouter « de NumberOfStates » à l'objet Pays . Il va optimiser la requête, mais vous devrez faire que le champ supplémentaire contient les informations correctes.

  • Si vous utilisez NHibernate, vous pouvez utiliser ExtraLazyLoading - il sera question une autre sélection, mais ne remplira pas la collection entière quand comte est appelé. Plus d'infos ici: nHibernate Collection Count

+0

Dans votre deuxième approche, cela ne mène-t-il pas à n + 1 (une sélection pour obtenir la liste des pays, puis une sélection pour chaque pays)? – Sam

+0

Oui, il émettra une requête de compte pour chaque pays. Si vous avez beaucoup de pays, ce n'est pas une bonne solution. – Vladikk

2

Cela dépend vraiment de l'architecture des projets pour ce que nous faisons. Habituellement cependant .. nous avons des services au-dessus des dépôts qui manipulent cette logique pour vous. Le service décide quels référentiels utiliser pour charger les données. Le flux est UI -> Contrôleur -> Service -> Référentiels -> DB. L'interface utilisateur et/ou les contrôleurs n'ont aucune connaissance des référentiels ou de leur implémentation.

Aussi, StateCount = c.States.Count peuplerait sans aucun doute la liste des États de toute façon .. ne serait-il pas? Je suis à peu près sûr que cela sera dans NHibernate (avec LazyLoading provoquant une sélection supplémentaire à envoyer à la DB).

+0

Cela fait un moment, mais j'ai pensé que lorsque j'ai vu la trace de StateCount = c.States.Count, il a créé un agrégat au lieu de charger la collection. Je vais vérifier à nouveau pour voir si c'est le cas. – Sam

2

Une option consiste à séparer entièrement vos requêtes de votre infrastructure existante. Ce serait une implémentation d'un design CQRS. Dans ce cas, vous pouvez émettre une requête directement dans la base de données en utilisant un "Thin Read Layer", en ignorant les objets de votre domaine. Vos objets existants et ORM vous gênent réellement, et CQRS vous permet d'avoir un «côté commande» séparé et peut-être un ensemble de technologies totalement différent de votre «côté requête», où chacun est conçu pour faire son propre travail sans être compromis par les exigences de l'autre.

Oui, je suis tout à fait littéralement ce qui suggère de laisser votre seule architecture existante, et peut-être utiliser quelque chose comme Dapper pour ce faire (attention de l'échantillon de code non testé) directement à partir de vos contrôleurs MVC, par exemple:

int count = 
    connection.Query<int>(
     "select count(*) from state where countryid = @countryid", 
     new { countryid = 123 }); 
+1

Pour être honnête, l'infrastructure est à ses balbutiements. Je suis ouvert à essayer de nouvelles choses. J'ai essayé d'embrasser l'ensemble de l'abstraction de couche de n-tier, mais il semble que, quoi que je fasse, il y a des fuites entre couches. Je regarderai les choses du CQRS plus tard ce soir. L'ORM avec lequel je joue actuellement est NPoco (branche de PetaPoco). Il est similaire à Dapper et est assez flexible. Ce qui fait obstacle au progrès, c'est cette idée des modèles de Repository et d'abstention de tout pour la testabilité. C'est un PITA majeur pour tout sauf le plus simple des exemples à mon humble avis. – Sam

+1

Si vous appliquez un style DDD à la persistance de votre objet de domaine en définissant des limites globales, assurez-vous que votre contrat de référentiel autorise uniquement Get (id) et Add (object) et laissez toutes les requêtes à une tranche verticale totalement distincte (avec ses propres couches, mais probablement seulement un ou deux plutôt que la quantité typique) alors vous devriez être en or. –

Questions connexes