2010-07-12 4 views
3

Comme nous le savons tous, la plupart des applications ont une couche d'accès aux données, utilisant souvent des classes de référentiel. Typiquement, nous voulons que le référentiel fonctionne avec fortement typé objets, par ex. Toutefois, nous souhaitons parfois créer une requête plus complexe sur une base de données, impliquant le regroupement et l'agrégation. Par exemple, nous voulons récupérer une valeur totale de tous les ordres de tous les utilisateurs (le jeu de résultats n'aura que deux colonnes: UserName et TotalAmount). Maintenant, ma question: à quoi ressemblerait la méthode du dépôt? Il y a des douzaines d'exemples sur internet de LINQ aux entités comment faire une requête avec sum et group by, mais tous ces exemples retournent des types anonymes. Alors, comment notre référentiel peut-il renvoyer le résultat d'une telle requête? L'autre réponse commune est: enveloppez-le dans une classe. Ainsi, la méthode pourrait ressembler à ceci:LINQ aux entités - bonnes pratiques pour passer des modèles

interface IUserRepository 
{ 
    UserOrderTotal[] GetOrderTotalsForAllUsers(); 
} 

UserOrderTotal devrait être une petite classe avec les deux propriétés renvoyées par la requête: nom d'utilisateur et TotalAmount. Cette classe serait définie quelque part dans un de nos fichiers .cs. Est-ce vraiment la seule solution? Cela semble très mauvais parce qu'il introduit un nouveau modèle «temporaire» quelque part en dehors du modèle des entités principales (edmx). Idéalement, je voudrais d'enrichir mon modèle utilisateur avec un nouveau champ, appelé TotalAmount, qui ne serait renseigné dans cette requête, donc l'interface pourrait ressembler à ceci:

interface IUserRepository 
{ 
    User[] GetOrderTotalsForAllUsers(); 
} 

Et ce serait de retourner des entités de l'utilisateur avec tous champs définis par défaut, à l'exception du nom et TotalAmount. Le problème que j'ai est que je ne peux pas compiler un modèle avec champ qui n'est pas mappé à une colonne de base de données. J'obtiens erreur Erreur 3004: Problème dans les fragments de mappage à partir de la ligne 2226: Aucun mappage spécifié pour les propriétés User.TotalAmount dans Set User.An Entité avec clé (PK) ne va pas aller-retour lorsque: Entité est de type [FPSoMeterModel.User ]

Est-ce que je le fais mal? Ce problème est-il trivial, ou peut-être que je pose de mauvaises questions? Faire une classe wrapper pour chaque requête (!) Qui implique l'agrégation semble un peu ridicule. Il semble que tout le contenu de LINQ encourage les gens à ne PAS utiliser une architecture multi-tiers, mais à construire des requêtes dans la même couche où les données sont rendues ... Comment les gars vous occupez-vous de cela, utilisez-vous des classes "repository" avec LINQ, si oui - comment renvoyez-vous des résultats de requête complexes?

Répondre

1

Je ne considérerais pas définir une nouvelle mauvaise pratique de classe UserOrderTotal, au contraire. Il y a deux façons de regarder un tel objet.

(1) Vous pouvez voir cette classe UserOrderTotal comme Data Transfer Object (DTO). De cette façon, cela ne fait pas partie de votre domaine. Les DTO sont normalement utilisés pour transférer des entités sur le réseau ou pour fournir des objets de domaine sous une forme aplatie à la couche de présentation. Je les utilise tout le temps. Dans cette situation, l'objet est utilisé comme une projection sur les données: en tant que vue. Le modèle MVVM utilise le même principe lorsque la partie ViewModel de MVVM est une interface DTO spécifique à l'interface utilisateur. Cette classe UserOrderTotal fait partie de votre domaine Du point de vue de la conception orientée domaine, l'objet pourrait faire partie du langage des utilisateurs. Dans cette situation, il serait logique de le définir comme objet de domaine. La différence est cependant qu'elle ne sera pas définie dans le fichier edmx de Entity Framework (EF). Je pense qu'il est possible dans EF de définir des entités qui n'ont pas de mappage vers une table de base de données, mais je ne sais pas si cela facilite les choses. Personnellement, je ne vois pas de problème à définir ces objets de domaine en dehors du fichier edmx dans le même projet que celui où se trouve edmx. A propos du renvoi d'un tableau d'objets User: Cela semble être une mauvaise idée.Il y a plusieurs problèmes avec cette approche. Tout d'abord, votre code ne communique pas très bien son intention. Vous utilisez un objet User, mais laissez toutes les propriétés vides. Cette pratique est très difficile à suivre pour quiconque essaie d'utiliser ce code. Vous abusez en fait de l'objet User pour quelque chose qu'il n'est pas: à savoir un agrégat. En outre, vous avez remarqué que EF ne peut pas gérer cette propriété supplémentaire que vous avez ajoutée au modèle. Alors qu'en général il est possible d'ajouter des propriétés au modèle EF (en utilisant des classes partielles), vous devriez être très conservateur à ce sujet. Alors que les choses vont compiler les choses vont échouer au moment de l'exécution lorsque vous utilisez ces propriétés dans les requêtes LINQ.

Faire une classe wrapper pour chaque requête (!) Qui implique l'agrégation semble un peu ridicule .

Je ne pense pas. OMI c'est la bonne chose à faire. C'est la conséquence de l'utilisation de EF dans une architecture multicouche. Je dois admettre que le fait d'avoir EF en une seule couche est beaucoup plus facile, mais à long terme, ça devient vraiment désordonné. Plus le projet est important et plus longtemps vous en avez besoin, plus les couches d'abstractions sont justifiées.

Comment les gars traitez-vous ce, avez-vous utiliser « référentiel » classes avec LINQ

L'application que je l'ai conçu, ont souvent un service/couche d'affaires qui sépare les lectures de la mutations. Pour les opérations/requêtes de lecture, j'utilise des classes statiques avec des méthodes qui renvoient une collection d'objets de domaine ou de DTO. Souvent, une telle «méthode de service» est spécifique à une partie particulière de l'interface utilisateur. Les mutations sont enveloppées dans des «commandes de service». L'interface utilisateur crée une commande de service et la déclenche. Une commande exprime un cas d'utilisation unique ou une histoire d'utilisateur et est un morceau de logique atomique. La couche de service assurera la création d'une transaction de base de données (si nécessaire) lorsque la commande est exécutée. Pour l'interface utilisateur, l'utilisation d'une commande ressemble généralement à ceci:

void UpdateButton_Click(object sender, EventArgs e) 
{ 
    if (!this.Page.IsValid) 
    { 
     return; 
    } 

    var command = new UpdateUserSettingsCommand(); 

    command.UserName = this.UserName.Text; 
    command.MailAddress = this.MailAddress.Text; 

    command.Execute(); 
} 

J'espère que tout cela a du sens. Si vous voulez en savoir plus sur la façon dont j'utilise les DTO, lisez this SO answer of mine.

J'espère que cela aide.

Questions connexes