Entity Framework和SQL Server视图

141

因为我没有权利谈论几个原因,所以我们正在以下方式定义我们的 Sql Server 2005 数据库视图:

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

这个想法是,Entity Framework将基于此查询创建一个实体,它确实这样做了,但它生成的实体带有以下错误:

警告6002:表/视图“Keystone_Local.dbo.MeterProvingStatisticsPoint”没有定义主键。密钥已被推断,并且定义为只读表/视图。

并且它决定CompletedDateTime字段将是此实体的主键。

我们正在使用EdmGen生成模型。有没有办法使Entity Framework不将该视图的任何字段包含为主键?

9个回答

255

我们遇到了同样的问题,这是解决方法:

要强制实体框架使用列作为主键,请使用ISNULL。

要强制实体框架不使用列作为主键,请使用NULLIF。

一个简单的应用方法是将视图的选择语句包装在另一个选择语句中。

示例:

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

2
我认为这是最好的期望。归根结底,它有效。 - MvcCmsJon
3
这里唯一的问题就是可能需要让“view”返回一个空字符串''。我所做的,就是将该列重新转换为它本身的数据类型。例如,如果AnotherProperty的数据类型是varchar(50),那么我会这样转换:“CONVERT(VARCHAR(50), AnotherProperty) AS [AnotherProperty]”。这掩盖了EF中的空值,并允许使用空字符串。 - Bart
2
是的,这个可以工作。例如,可以将该列用作EF的主键: isnull(CONVERT(VARCHAR(50), newid()),'') AS [PK] - dc2009
2
除了解决方案中有一个烦人的消息之外,不修复它会有任何危害吗?我同意你的解决方案,但坦白地说,我不觉得我应该这样做 - 我认为我们都可以同意这是一个错误,对吧? - dyslexicanaboko
1
我们现在已经解决了这个问题,但是你需要重新启动VS2013才能消除错误。非常烦人。 - Gordon Thompson
显示剩余8条评论

68

我能够通过使用设计器来解决这个问题。

  1. 打开“模型浏览器”。
  2. 在图表中查找视图。
  3. 右键单击主键,并确保“实体键”已选中。
  4. 多选所有非主键。使用Ctrl或Shift键。
  5. 在属性窗口中(如果需要,请按F4查看),将“实体键”下拉菜单更改为False。
  6. 保存更改。
  7. 关闭Visual Studio并重新打开它。我正在使用带有EF 6的Visual Studio 2013,我必须这样做才能消除警告。

我不需要更改我的视图来使用ISNULL、NULLIF或COALESCE解决方法。如果您从数据库更新模型,则警告将重新出现,但如果您关闭并重新打开VS,则警告将消失。设计器中所做的更改将被保留,不受刷新影响。


9
确认。必须重新启动VS2013才能消除警告。 - Michael Logutov
5
“你试过关闭再重新开启吗?”;-) 谢谢,这么做非常有效! - Obl Tobl
5
当我创建视图时,它们甚至无法出现在模型图中。它们会被注释在 XML 文件中。 - Luis Deras
2
确认需要重新启动VS2017才能消除警告。 - Marc Levesque
在使用Visual Studio 2022和Entity Framework 6(我还使用了Oracle.ManagedDataAccess.EntityFramework)时,没有主键的表在Portal下不会显示,只会显示Portal.Store。在这个版本中,没有办法在设计器中更改"ENTITY KEY"(而不需要在像Visual Studio Code这样的XML编辑器中编辑文件)。在这个版本中,这个修复方法不起作用。 - undefined
显示剩余3条评论

46

同意@Tillito的观点,但在大多数情况下,它会破坏SQL优化器,导致它无法使用正确的索引。

对于某些人来说可能很明显,但我曾经因为使用了Tillito的解决方案而浪费了几个小时来解决性能问题。假设你有这样一个表:

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

你的视图应该长成这个样子

 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

SQL优化器将不使用索引ix_customer,而是在主索引上执行表扫描,但如果改成:

Group by CustomerId

你使用

Group by IsNull(CustomerId, -1)

这将使得 MS SQL(至少2008版本)在计划中包含正确的索引。

如果


2
这应该是对Tillito答案的评论,而不是一个答案,因为它没有为OP的问题提供解决方案。 - zimdanen
6
此人声望值为1,暂不能添加评论。 - jrcs3
@zimdanen,你不可能把所有这些信息都放在评论里,把它们放在一个单独的回答中更有意义。 - Contango
2
@Contango:这个答案在发布六天后被编辑了,而我则是在发表评论之后。请查看修订历史记录。 - zimdanen

10

这种方法对我很有效。我在主键字段上使用ISNULL(),如果该字段不应该是主键但也应具有非空值,则使用COALESCE()。此示例生成具有非空主键的ID字段。其他字段不是主键,并且将它们的可为空属性设置为(None)。

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

如果您真的没有主键,可以使用ROW_NUMBER生成一个被您的代码忽略的伪键来欺骗一个。例如:

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

是的,我最终使用了 NEWID() as id 进行欺骗,但这是相同的想法。而且有合法的用例 - 如果你有一个只读视图,比如说。丑陋的 EF,真丑。 - ruffin

5

当前的Entity Framework EDM生成器将从视图中所有非空字段创建复合键。如果要对此进行控制,则需要修改视图和基础表列,将不想作为主键的列设置为可空。相反的情况也是如此,正如我遇到的那样,EDM生成的键会导致数据重复问题,因此我不得不将可空列定义为非空列,以强制EDM中的复合键包括该列。


我们在推断主键方面遇到了相同的问题,该实体返回重复的记录,非常令人烦恼。如果执行 Context.Entity.ToList() 会得到重复记录,但如果直接执行 EF 生成的 SQL 查询(使用 LINQPad 获得),则不会发生记录重复。似乎存在将数据库记录映射到返回的实体对象(POCO)时出现问题,因为使用解释逻辑(非可空列)推断 PK。 - David Oliván

3
为了获取一个视图,我只需要显示一个我创建的主键列。我创建了第二个视图,指向第一个视图,并使用NULLIF使类型可为空。这对我有用,使EF认为视图中只有一个主键。
不确定这是否会对你有帮助,因为我不认为EF会接受没有主键的实体。

3

如果您不想混淆应该是主键的内容,我建议:

  1. ROW_NUMBER纳入您的选择中
  2. 将其设置为主键
  3. 在模型中将所有其他列/成员设置为非主键

3

有道理。那么,我们是否可以像定义表一样在视图中定义列为非空或可空的呢? - Sergio Romero
1
抱歉,我在Entity Framework方面已经超出了我的专业水平。 :-) - RBarryYoung
1
有人知道这个问题什么时候会被修复吗?当你有非空列而不是主键时,不得不绕过这个问题真的很烦人。 - live-love

1
由于上述问题,我更喜欢表值函数。
如果你有这个:
CREATE VIEW [dbo].[MyView] AS SELECT A, B FROM dbo.Something

create this:

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

那么您只需要导入函数而不是视图。


2
你会如何使用这种方法在实体之间创建关联?这是可能的吗? - Luis Deras

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接