7

Étant donné un « utilisateur » table et une table « Connexion » dans MS SQL 2008:Comment ajouter une colonne calculée à mon modèle EF4?

CREATE TABLE [dbo].[User_User](
    [UserID] [int] IDENTITY(1000,1) NOT NULL, 
    [UserName] [varchar](63) NOT NULL, 
    [UserPassword] [varchar](63) NOT NULL 
) 
CREATE TABLE [dbo].[Util_Login](
    [LoginID] [int] IDENTITY(1000,1) NOT NULL, 
    [User_UserID] [int] NOT NULL, -- FK REFERENCES [dbo].[User_User] ([UserID]) 
    [LoginDate] [datetime] NOT NULL, 
) 

Comment puis-je régler mon inclure une colonne « UserLastLogin » User_User entité objet modèle-cadre qui renvoie un MAX (LoginDate)?

Je sais que je peux créer un modèle EF4 autour d'une vue SQL:

CREATE VIEW [v_User_User] 
AS 
SELECT 
     [User_User].*, 
     (
       SELECT MAX(LoginDate) 
       FROM [Util_Login] 
       WHERE User_UserID = UserID 
     ) AS UserLastLogin 
FROM [User_User] 

Mais est-il possible que je peux simplement modifier le modèle de User_User pour inclure la columnn calculée?

EDIT: Je suis à la recherche d'un moyen de chercher un utilisateur ou une liste <utilisateur> y compris la date Max (Util.LastLogin) dans une seule requête db.

Répondre

5

Très bonne question, et oui, il y a une meilleure façon d'y arriver en EF4:

propriétés personnalisées sont un moyen de fournir des propriétés calculées aux entités. Les bonnes nouvelles sont que les propriétés personnalisées ne doivent pas nécessairement être calculées à partir d'autres propriétés existantes sur la même entité, par le code que nous allons voir, ils peuvent être calculés à partir de presque tout ce que nous aimons!

Voici les étapes:
d'abord créer une classe partielle et définir une propriété personnalisée sur elle (Par souci de simplicité, je supposais tableau User_User a été mis en correspondance avec la classe utilisateur et Util_Login à Util)

public partial class User { 
    public DateTime LastLoginDate { get; set; } 
} 

donc, comme vous pouvez le voir ici, plutôt que de créer une propriété dateConnexionPrecedente dans le modèle, ce qui serait nécessaire pour la carte au magasin de données, nous avons créé la propriété dans la classe partielle et nous ha ai la possibilité de le remplir pendant objet matérialisation ou sur demande si vous ne croyez pas que chaque objet entité devra fournir cette information.

Dans votre cas précalculer la dateConnexionPrecedente propriété personnalisé pour chaque utilisateur étant matérialisée est utile, car je pense que cette valeur sera accessible pour tous (ou au moins la plupart) des entités étant matérialisées. Sinon, vous ne devriez envisager de calculer la propriété qu'au besoin et non pendant la matérialisation de l'objet. Pour cela, nous allons tirer parti de ObjectContext.ObjectMaterialized Event qui est déclenché à chaque fois que des données sont renvoyées à partir d'une requête car ObjectContext crée les objets d'entité à partir de ces données. L'événement ObjectMaterialized est une chose Entity Framework 4. Tout ce que nous devons faire est de créer un gestionnaire d'événements et de l'abonner à l'événement ObjectMaterialized.

Le meilleur endroit pour mettre ce code (abonnement à l'événement) est à l'intérieur de la OnContextCreated Méthode.Cette méthode est appelée par le constructeur de l'objet de contexte et le constructeur surcharges qui est une méthode partielle sans implémentation, simplement une signature de méthode créée par un générateur de code EF.

Ok, maintenant vous devez créer une classe partielle pour votre ObjectContext. (Je suppose que le nom est UsersAndLoginsEntities) et abonnez le gestionnaire d'événements (je l'ai appelé Context_ObjectMaterialized) à ObjectMaterialized Event.

public partial class UsersAndLoginsEntities { 
    partial void OnContextCreated() { 
     this.ObjectMaterialized += Context_ObjectMaterialized; 
    } 
} 

La dernière étape (le vrai travail) serait de mettre en œuvre ce gestionnaire pour remplir effectivement la propriété personnalisée pour nous, qui dans ce cas est très simple:

void Context_ObjectMaterialized(object sender, ObjectMaterializedEventArgs args) 
{ 
    if (args.Entity is User) {   
     User user = (User)args.Entity; 
     user.LastLoginDate = this.Utils 
       .Where(u => u.UserID == user.UserID) 
       .Max(u => u.LoginDate); 
    } 
} 


Espoir CA aide.

+0

Merci pour la réponse verbeuse! Un problème que je vois est qu'il faudra une requête sql pour définir le LastLoginDate pour chaque utilisateur. par exemple. Si j'ai une liste de 50 utilisateurs, cela signifie une requête pour remplir la liste et 50 requêtes supplémentaires pour remplir le LastLoginDate pour chaque utilisateur. Ou est-ce que je me trompe à propos de l'événement ObjectMaterialized? – uhleeka

+1

C'est exact, l'événement est déclenché pour chaque objet utilisateur dans la liste.Si cela n'est pas souhaitable, l'autre option consisterait à charger avec impatience la propriété de navigation Util, puis à faire un Max() sur le côté client. –

+0

Ouais, j'ai regardé impatiemment le chargement un peu ... une charge ardente de l'Util ne retournerait-elle pas toutes les lignes Util associées à l'Utilisateur? Donc, essentiellement, un client côté Max() jetterait tout sauf la première rangée. – uhleeka

1

Après de longues délibérations, j'ai fini avec la solution suivante:

D'abord, créer une vue contenant tous les champs de l'utilisateur plus un champ de date de lastlogin (de mon poste d'origine).

Après avoir ajouté l'utilisateur (appeler User_Model) et la vue de l'utilisateur (appeler UserView_Model) à mon modèle EF, j'ai créé une classe wrapper (appeler User_Wrapper) autour de la User_Model et a ajouté un montant supplémentaire Propriété DateTime pour LastLogin.

J'ai modifié la classe User_Wrapper pour récupérer à partir du UserView_Model, puis remplir le User_Model sous-jacent en reflétant toutes les propriétés partagées entre User_Model et UserView_Model. Enfin, je définis la propriété User_Wrapper.LastLogin en fonction de l'User_View récupérée.

Toutes les autres fonctions (Créer, Mettre à jour, Supprimer ...) fonctionnent sur le User_Model. Seul le Fetch utilise le UserView_Model.


Qu'est-ce que tout cela fait? J'ai maintenant seulement un appel de base de données pour remplir un seul User_Wrapper ou une liste <User_Wrapper>.

Les inconvénients? Je suppose que parce que mon UserView_Model n'a aucune relation associée, je ne serais pas en mesure de faire un chargement impatient en utilisant le ObjectContext EF. Heureusement, dans ma situation, je ne trouve pas que ce soit un problème.

Y a-t-il un meilleur moyen?

1

Je venais juste d'avoir une situation où j'avais besoin de compter les propriétés de deux entités liées sans charger les collections. Une chose que j'ai découverte est que vous devez avoir MultipleActiveResultSets = True dans la chaîne de connexion pour éviter qu'une exception soit levée sur le gestionnaire d'événements ObjectMaterialized lors de l'interrogation d'autres entitycollections.