Entity Framework y SQL Server View

132

Por varias razones de las que no tengo libertad para hablar, estamos definiendo una vista en nuestra base de datos Sql Server 2005 de la siguiente manera:

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

La idea es que Entity Framework creará una entidad basada en esta consulta, lo que hace, pero la genera con un error que establece lo siguiente:

Advertencia 6002: La tabla / vista 'Keystone_Local.dbo.MeterProvingStatisticsPoint' no tiene una clave principal definida. Se ha inferido la clave y la definición se creó como una tabla / vista de solo lectura.

Y decide que el campo CompletedDateTime será la clave principal de esta entidad.

Estamos usando EdmGen para generar el modelo. ¿Hay alguna manera de que el marco de la entidad no incluya ningún campo de esta vista como clave principal?

Sergio Romero
fuente

Respuestas:

245

Tuvimos el mismo problema y esta es la solución:

Para obligar al marco de la entidad a usar una columna como clave principal, use ISNULL.

Para forzar que el marco de la entidad no use una columna como clave principal, use NULLIF.

Una manera fácil de aplicar esto es envolver la declaración select de su vista en otra selección.

Ejemplo:

SELECT
  ISNULL(MyPrimaryID,-999) MyPrimaryID,
  NULLIF(AnotherProperty,'') AnotherProperty
  FROM ( ... ) AS temp
Tillito
fuente
2
Creo que esto es lo mejor que se puede esperar. En pocas palabras funciona.
MvcCmsJon
1
¡Gracias! Funcionó perfectamente. @sabanito Creo que analiza la definición. Es por eso que necesita envolver específicamente las propiedades clave en IsNull (). Tengo una vista que no devuelve ningún valor nulo (y no puede devolver ningún valor nulo), pero debido a la forma en que se escribió la lógica, EF no pudo determinar que ese fuera el caso hasta que envolví las claves en IsNull ().
Rabino
3
El único problema que veo aquí es que la vista podría legítimamente necesitar devolver una cadena vacía ''. Lo que hice fue simplemente devolver la columna a su propio tipo de datos. por ejemplo, si AnotherProperty tenía un tipo de datos varchar (50), lo convertiría como tal 'CONVERT (VARCHAR (50), AnotherProperty) AS [AnotherProperty]'. esto ocultaba la nulabilidad de EF y también permitía cadenas vacías.
Bart
2
sí, esto funciona, por ejemplo, para hacer que EF use la columna como una clave primaria isnull (CONVERT (VARCHAR (50), newid ()), '') AS [PK]
dc2009
2
Aparte de que solo hay un mensaje molesto en la solución, ¿hay algún daño en no solucionarlo? Estoy de acuerdo con su solución, pero, francamente, no creo que deba hacerlo, creo que todos podemos estar de acuerdo en que es un error, ¿verdad?
dyslexicanaboko
67

Pude resolver esto usando el diseñador.

  1. Abre el navegador de modelos.
  2. Encuentra la vista en el diagrama.
  3. Haga clic derecho en la clave primaria y asegúrese de que la "Clave de entidad" esté marcada.
  4. Selección múltiple de todas las claves no primarias. Use las teclas Ctrl o Shift.
  5. En la ventana Propiedades (presione F4 si es necesario para verlo), cambie el menú desplegable "Clave de entidad" a Falso.
  6. Guardar cambios.
  7. Cierre Visual Studio y vuelva a abrirlo. Estoy usando Visual Studio 2013 con EF 6 y tuve que hacer esto para que las advertencias desaparecieran.

No tuve que cambiar mi punto de vista para usar las soluciones ISNULL, NULLIF o COALESCE. Si actualiza su modelo desde la base de datos, las advertencias volverán a aparecer, pero desaparecerán si cierra y vuelve a abrir VS. Los cambios que realizó en el diseñador se conservarán y no se verán afectados por la actualización.

Casey Plummer
fuente
9
Confirmado. Tiene que reiniciar VS2013 para que la advertencia desaparezca.
Michael Logutov
55
"¿Has intentado apagarlo y volver a encenderlo?" ;-) Gracias, funciona como un encanto!
Obl Tobl
44
Cuando estoy creando vistas, ni siquiera llegan a estar en el diagrama del modelo. Están comentados en el archivo xml
ggderas
¡Una solución simple y fácil y no parece ser una solución no hacky como manipular la vista! Gracias.
LuqJensen
2
El VS2017 confirmado debe reiniciarse también para que la advertencia desaparezca.
Marc Levesque
46

De acuerdo con @Tillito, sin embargo, en la mayoría de los casos dañará el optimizador de SQL y no usará los índices correctos.

Puede ser obvio para alguien, pero pasé horas resolviendo problemas de rendimiento con la solución Tillito. Digamos que tienes la mesa:

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

y tu punto de vista es algo como esto

 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

El optimizador SQL no usará index ix_customer y realizará un escaneo de tabla en el índice primario, pero si en lugar de:

Group by CustomerId

tu usas

Group by IsNull(CustomerId, -1)

hará que MS SQL (al menos 2008) incluya el índice correcto en el plan.

Si

Val Bakhtin
fuente
2
Esto debería ser un comentario sobre la respuesta de Tillito, no una respuesta en sí misma, ya que no proporciona una solución para la pregunta del OP.
zimdanen
66
El tipo tiene un representante de 1, todavía no puede agregar un comentario.
jrcs3
@zimdanen No hay forma de que pueda incluir toda esta información en un comentario, tiene más sentido tenerla en una respuesta por separado.
Contango
2
@Contango: esta respuesta se editó seis días después de que se publicó y publiqué mi comentario. Ver el historial de revisiones.
zimdanen
9

Este método me funciona bien. Uso ISNULL () para el campo de clave principal y COALESCE () si el campo no debe ser la clave principal, sino que también debe tener un valor no anulable. Este ejemplo produce un campo ID con una clave primaria no anulable. Los otros campos no son claves y tienen (Ninguno) como su atributo Anulable.

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 realmente no tiene una clave principal, puede suplantarla utilizando ROW_NUMBER para generar una pseudoclave que su código ignora. Por ejemplo:

SELECT
ROW_NUMBER() OVER(ORDER BY A,B) AS Id,
A, B
FROM SOMETABLE
SpazDude
fuente
Sí, terminé haciendo trampa NEWID() as id, pero es la misma idea. Y hay casos de uso legítimos, por ejemplo, si tiene una vista de solo lectura. Feo, EF, feo.
ruffin
4

El generador de EDM de Entity Framework actual creará una clave compuesta a partir de todos los campos no anulables en su vista. Para obtener el control sobre esto, deberá modificar la vista y las columnas de la tabla subyacente configurando las columnas como anulables cuando no desee que formen parte de la clave principal. Lo contrario también es cierto, como encontré, la clave generada por EDM estaba causando problemas de duplicación de datos, por lo que tuve que definir una columna anulable como no anulable para forzar que la clave compuesta en el EDM incluya esa columna.

Annagram
fuente
Tenemos el mismo problema con la PK inferida, la entidad devuelve registros duplicados y es completamente molesta. Si ejecuta Context.Entity.ToList()registros duplicados, pero si ejecuta la consulta SQL generada por EF directamente (obtenida con LINQPad), no se produce la duplicación de registros. Parece ser un problema al asignar los registros de la base de datos a los objetos de entidad (POCO) devueltos, ya que la PK se infiere usando la lógica explicada (columnas no anulables).
David Oliván Ubieto
3

Parece que es un problema conocido con EdmGen: http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/thread/12aaac4d-2be8-44f3-9448-d7c659585945/

RBarryYoung
fuente
Eso tiene sentido. Entonces, ¿hay alguna manera de definir una columna como no nula o nula en una vista de la forma en que la estamos definiendo?
Sergio Romero
1
Lo siento, ya estoy más allá de mi nivel de experiencia en Entity Framework. :-)
RBarryYoung
1
¿Alguien sabe cuándo se solucionará este problema? Es molesto tener que solucionar esto cuando tiene columnas no nulas que no son claves principales.
live-love
3

Para obtener una vista, solo tenía que mostrar una columna de clave principal. Creé una segunda vista que apuntaba a la primera y usé NULLIF para hacer que los tipos fueran anulables. Esto me funcionó para hacer que el EF pensara que solo había una clave principal en la vista.

Sin embargo, no estoy seguro de si esto lo ayudará, ya que no creo que el EF acepte una entidad sin clave primaria.

Nick Gotch
fuente
3

Si no desea meterse con lo que debería ser la clave principal, le recomiendo:

  1. Incorporar ROW_NUMBERa su selección
  2. Establecerlo como clave principal
  3. Establecer todas las demás columnas / miembros como no primarios en el modelo
Santhos
fuente
1

Debido a los problemas mencionados anteriormente, prefiero las funciones de valor de tabla.

Si tienes esto:

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

crea esto:

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

Luego simplemente importa la función en lugar de la vista.

Rayo
fuente
2
¿Cómo crearía asociaciones entre entidades que siguen este enfoque? ¿Es posible?
ggderas