Entity Framework et vue SQL Server


132

Pour plusieurs raisons dont je n'ai pas la liberté de parler, nous définissons une vue sur notre base de données Sql Server 2005 comme ceci:

CREATE VIEW [dbo].[MeterProvingStatisticsPoint]
AS
SELECT
    CAST(0 AS BIGINT) AS 'RowNumber',
    CAST(0 AS BIGINT) AS 'ProverTicketId',
    CAST(0 AS INT) AS 'ReportNumber',
    GETDATE() AS 'CompletedDateTime',
    CAST(1.1 AS float) AS 'MeterFactor',
    CAST(1.1 AS float) AS 'Density',
    CAST(1.1 AS float) AS 'FlowRate',
    CAST(1.1 AS float) AS 'Average',
    CAST(1.1 AS float) AS 'StandardDeviation',
    CAST(1.1 AS float) AS 'MeanPlus2XStandardDeviation',
    CAST(1.1 AS float) AS 'MeanMinus2XStandardDeviation'
WHERE 0 = 1

L'idée est que Entity Framework créera une entité basée sur cette requête, ce qu'il fait, mais il la génère avec une erreur indiquant ce qui suit:

Avertissement 6002: La table / vue 'Keystone_Local.dbo.MeterProvingStatisticsPoint' n'a pas de clé primaire définie. La clé a été déduite et la définition a été créée en tant que table / vue en lecture seule.

Et il décide que le champ CompletedDateTime sera la clé primaire de cette entité.

Nous utilisons EdmGen pour générer le modèle. Existe-t-il un moyen de ne pas inclure dans le cadre d'entité un champ de cette vue en tant que clé primaire?

Réponses:


245

Nous avons eu le même problème et voici la solution:

Pour forcer la structure d'entité à utiliser une colonne comme clé primaire, utilisez ISNULL.

Pour forcer la structure d'entité à ne pas utiliser une colonne comme clé primaire, utilisez NULLIF.

Un moyen simple d'appliquer cela consiste à envelopper l'instruction select de votre vue dans une autre sélection.

Exemple:

SELECT
  ISNULL(MyPrimaryID,-999) MyPrimaryID,
  NULLIF(AnotherProperty,'') AnotherProperty
  FROM ( ... ) AS temp

2
Je pense que c'est le meilleur que l'on puisse espérer. En bout de ligne, cela fonctionne.
MvcCmsJon

1
Merci! Cela a parfaitement fonctionné. @sabanito Je pense que cela analyse la définition. c'est pourquoi vous devez spécifiquement encapsuler les propriétés clés dans IsNull (). J'ai une vue qui ne renvoie aucune valeur nulle (et ne peut renvoyer aucune valeur nulle) mais en raison de la façon dont la logique a été écrite, EF n'a pas pu déterminer que c'était le cas jusqu'à ce que j'enveloppe les clés dans IsNull ().
Rabbi

3
Le seul problème que je vois ici est que la vue pourrait légitimement avoir besoin de renvoyer une chaîne vide ''. Ce que j'ai fait, c'est simplement reconstituer la colonne à son propre type de données. par exemple, si AnotherProperty avait un type de données varchar (50), je le convertirais comme tel 'CONVERT (VARCHAR (50), AnotherProperty) AS [AnotherProperty]'. cela masquait la possibilité de nullité d'EF et autorisait également les chaînes vides.
Bart

2
oui cela fonctionne par exemple pour que EF utilise la colonne comme clé primaire isnull (CONVERT (VARCHAR (50), newid ()), '') AS [PK]
dc2009

2
Hormis le fait qu'il y ait simplement un message ennuyeux dans la solution, y a-t-il un mal à ne pas résoudre ce problème? Je suis d'accord avec votre solution, mais franchement, je ne pense pas que je devrais avoir à le faire - je pense que nous pouvons tous convenir que c'est un bug, n'est-ce pas?
dyslexicanaboko

67

J'ai pu résoudre ce problème en utilisant le concepteur.

  1. Ouvrez le navigateur de modèles.
  2. Recherchez la vue dans le diagramme.
  3. Cliquez avec le bouton droit sur la clé primaire et assurez-vous que "Clé d'entité" est cochée.
  4. Sélectionnez plusieurs fois toutes les clés non primaires. Utilisez les touches Ctrl ou Maj.
  5. Dans la fenêtre Propriétés (appuyez sur F4 si nécessaire pour l'afficher), modifiez la liste déroulante "Clé d'entité" sur False.
  6. Sauvegarder les modifications.
  7. Fermez Visual Studio et rouvrez-le. J'utilise Visual Studio 2013 avec EF 6 et j'ai dû le faire pour que les avertissements disparaissent.

Je n'ai pas eu à modifier ma vue pour utiliser les solutions de contournement ISNULL, NULLIF ou COALESCE. Si vous mettez à jour votre modèle à partir de la base de données, les avertissements réapparaissent, mais disparaissent si vous fermez et rouvrez VS. Les modifications que vous avez apportées dans le concepteur seront conservées et ne seront pas affectées par l'actualisation.


9
Confirmé. Il faut redémarrer VS2013 pour que l'avertissement disparaisse.
Michael Logutov

5
"Avez-vous essayé de l'éteindre et rallumer?" ;-) Merci, ça marche comme un charme!
Obl Tobl

4
Lorsque je crée des vues, elles ne figurent même pas dans le diagramme du modèle. Ils sont commentés dans le fichier xml
ggderas

Solution simple et facile et ne semble pas être une solution non hacky que la manipulation de la vue! Je vous remercie.
LuqJensen

2
Le VS2017 confirmé doit également être redémarré pour que l'avertissement disparaisse.
Marc Levesque

46

D'accord avec @Tillito, mais dans la plupart des cas, cela endommagera l'optimiseur SQL et n'utilisera pas les bons index.

Cela peut être évident pour quelqu'un, mais j'ai passé des heures à résoudre des problèmes de performances en utilisant la solution Tillito. Disons que vous avez la table:

 Create table OrderDetail
    (  
       Id int primary key,
       CustomerId int references Customer(Id),
       Amount decimal default(0)
    );
 Create index ix_customer on OrderDetail(CustomerId);

et votre point de vue est quelque chose comme ça

 Create view CustomerView
    As
      Select 
          IsNull(CustomerId, -1) as CustomerId, -- forcing EF to use it as key
          Sum(Amount) as Amount
      From OrderDetail
      Group by CustomerId

L'optimiseur SQL n'utilisera pas l'index ix_customer et il effectuera une analyse de table sur l'index primaire, mais si au lieu de:

Group by CustomerId

tu utilises

Group by IsNull(CustomerId, -1)

cela fera en sorte que MS SQL (au moins 2008) inclura le bon index dans le plan.

Si


2
Cela devrait être un commentaire sur la réponse de Tillito, pas une réponse en soi, car cela ne fournit pas de solution à la question du PO.
zimdanen

6
Le gars a un représentant de 1, il ne peut pas encore ajouter de commentaire.
jrcs3

@zimdanen Vous ne pouvez pas intégrer toutes ces informations dans un commentaire, il est plus logique de les avoir dans une réponse séparée.
Contango

2
@Contango: Cette réponse a été modifiée six jours après sa publication et j'ai posté mon commentaire. Voir l'historique des révisions.
zimdanen

9

Cette méthode fonctionne bien pour moi. J'utilise ISNULL () pour le champ de clé primaire et COALESCE () si le champ ne doit pas être la clé primaire, mais doit également avoir une valeur non nullable. Cet exemple génère un champ ID avec une clé primaire non Nullable. Les autres champs ne sont pas des clés et ont (Aucun) comme attribut Nullable.

SELECT      
ISNULL(P.ID, - 1) AS ID,  
COALESCE (P.PurchaseAgent, U.[User Nickname]) AS PurchaseAgent,  
COALESCE (P.PurchaseAuthority, 0) AS PurchaseAuthority,  
COALESCE (P.AgencyCode, '') AS AgencyCode,  
COALESCE (P.UserID, U.ID) AS UserID,  
COALESCE (P.AssignPOs, 'false') AS AssignPOs,  
COALESCE (P.AuthString, '') AS AuthString,  
COALESCE (P.AssignVendors, 'false') AS AssignVendors 
FROM Users AS U  
INNER JOIN Users AS AU ON U.Login = AU.UserName  
LEFT OUTER JOIN PurchaseAgents AS P ON U.ID = P.UserID

si vous n'avez vraiment pas de clé primaire, vous pouvez en usurper une en utilisant ROW_NUMBER pour générer une pseudo-clé ignorée par votre code. Par exemple:

SELECT
ROW_NUMBER() OVER(ORDER BY A,B) AS Id,
A, B
FROM SOMETABLE

Ouais, j'ai fini par tricher avec NEWID() as id, mais c'est la même idée. Et il y a des cas d'utilisation légitimes - si vous avez une vue en lecture seule, par exemple. Moche, EF, moche.
ruffin

4

Le générateur EDM Entity Framework actuel créera une clé composite à partir de tous les champs non nullables de votre vue. Afin de prendre le contrôle de cela, vous devrez modifier la vue et les colonnes de la table sous-jacente en définissant les colonnes sur nullable lorsque vous ne souhaitez pas qu'elles fassent partie de la clé primaire. L'inverse est également vrai, comme je l'ai rencontré, la clé générée par EDM causait des problèmes de duplication de données, j'ai donc dû définir une colonne Nullable comme non Nullable pour forcer la clé composite dans l'EDM à inclure cette colonne.


Nous avons le même problème avec le PK déduit, l'entité renvoie des enregistrements dupliqués et est complètement ennuyeux. Si vous exécutez Context.Entity.ToList()des enregistrements en double, mais si vous exécutez directement la requête SQL générée par EF (obtenue avec LINQPad), aucune duplication d'enregistrement ne se produit. Semble être un problème de mappage des enregistrements de base de données avec les objets d'entité (POCO) renvoyés, car le PK est déduit à l'aide de la logique expliquée (colonnes non Nullable).
David Oliván Ubieto

3

Ça a du sens. Alors, y a-t-il un moyen de définir une colonne comme non nulle ou non nulle dans une vue telle que nous la définissons?
Sergio Romero

1
Désolé, je dépasse déjà mon niveau d'expertise dans Entity Framework. :-)
RBarryYoung

1
Quelqu'un sait quand ce problème sera résolu? C'est ennuyeux de devoir contourner cela lorsque vous avez des colonnes non nulles qui ne sont pas des clés primaires.
live-love

3

Pour obtenir une vue, je ne devais afficher qu'une seule colonne de clé primaire, j'ai créé une deuxième vue qui pointait vers la première et utilisé NULLIF pour rendre les types nullables. Cela a fonctionné pour moi pour faire croire à l'EF qu'il n'y avait qu'une seule clé primaire dans la vue.

Je ne sais pas si cela vous aidera car je ne pense pas que l'EF acceptera une entité sans clé primaire.


3

Si vous ne voulez pas jouer avec ce que devrait être la clé primaire, je recommande:

  1. Incorporer ROW_NUMBERdans votre sélection
  2. Définissez-le comme clé primaire
  3. Définir toutes les autres colonnes / membres comme non principaux dans le modèle

1

En raison des problèmes mentionnés ci-dessus, je préfère les fonctions de valeur de table.

Si vous avez ceci:

CREATE VIEW [dbo].[MyView] AS SELECT A, B FROM dbo.Something

créez ceci:

CREATE FUNCTION MyFunction() RETURNS TABLE AS RETURN (SELECT * FROM [dbo].[MyView])

Ensuite, vous importez simplement la fonction plutôt que la vue.


2
Comment créeriez-vous des associations entre entités suivant cette approche? C'est possible?
ggderas
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.