实体框架:不带主键的表


165

我有一个现有的数据库,我想使用它来构建一个新的应用程序 EF4.0

有些表没有定义主键,因此当我创建新的实体数据模型时,会收到以下消息:

The table/view TABLE_NAME does not have a primary key defined 
and no valid primary key could be inferred. This table/view has 
been excluded. To use the entity, you will need to review your schema, 
add the correct keys, and uncomment it.

如果我想使用它们并修改数据,是否必须将PK添加到这些表中,或者是否有解决方法以便不必这样做?


21
引用Joe Celko的话:如果它没有主键,那么它不是表。到底为什么有人会创建没有主键的“常规”表?只需添加那些PK!你需要他们-而早于后来....
marc_s

4
如果它对此有意见,请查看此案例stackoverflow.com/a/10302066/413032
DavutGürbüz2012年

50
并非每个表都需要主键是完全正确的。经常不有用,但有效。混淆EF是一个很好的理由,而不是花费很多。;-)。
Suncat2000

25
想象一下,我无法在公司上修改数据库结构,而数据库结构是由不会更改表结构的人创建的,这种情况是可能的。
铁托

4
这正是我们所处的位置。我们必须使用没有主键的第三方Oracle数据库。
David Brower

Answers:


59

该错误表示它的意思。

即使您可以解决此问题,请相信我,您也不想这样做。可能会引入的令人困惑的错误数量令人震惊且令人震惊,更不用说您的性能可能会下降的事实。

不要解决这个问题。修复您的数据模型。

编辑:我已经看到很多人都在反对这个问题。这很好,我想,但要记住的是,OP问映射没有主键,而不是一个视图。答案还是一样。从可管理性,数据完整性和性能的角度出发,解决EF对表进行PK的需求是一个坏主意。

一些人评论说,由于它们正在映射到第三方应用程序,因此无法修复基础数据模型。这不是一个好主意,因为模型可以从您的下方更改。可以说,在这种情况下,您可能希望映射到视图,这又不是OP所要求的。


44
在常见的senario中达成共识,但在诸如LOG表这样的罕见senario中,您只需要尽快插入记录。检查唯一性和建立索引时,拥有PK可能是一个问题。另外,如果您的PK是IDENTITY,那么将生成的值返回给EF是另一个问题。改用GUID?生成时间和索引/排序是另一个问题!...在某些关键的OLTP传感器(例如Logging)中,没有PK是要点,而没有任何积极要点!
Mahmoud Moravej

5
@MahmoudMoravej:首先,不要混淆集群索引和主键的想法。他们不是同一回事。您可以在IDENTITY列上具有聚集索引的表上具有非常高性能的插入。如果遇到索引维护问题,则应正确分区表。保留没有聚簇索引的表也意味着您不能对它进行有效的碎片整理,以在删除后回收空间。如果可怜的人尝试查询您的日志表(如果它没有索引),我很可惜。
戴夫·马克

149
“修正您的数据模型”不是真正的答案。有时,我们不得不忍受没有创造且无法改变的不理想情况。而且,正如@Colin所说,有一种方法可以完全执行OP的要求。
TheSmurf 2012年

13
更改代码以满足EF本身就是一种解决方法。并非所有表都需要主键,也不应该强制它们。例如,您有一个主题,它有0个或多个关键字。关键字表可以具有父主题ID和相应的关键字。要说我需要重塑数据库是因为EF强迫我这样做是la脚的。
Mrchief 2012年

14
这应该被否决,因为它不能回答问题。我们经常需要使用无法更改的第三方数据库。
Kurren 2014年

104

我认为这是Tillito解决的:

实体框架和SQL Server视图

我将在下面引用他的条目:

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

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

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

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

例:

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

Tillito在2010年4月26日17:00回答


6
+1这是正确的答案,在完美的世界中,最好进入并修改所有旧数据库以具有参照完整性,但这实际上并不总是可能的。
迪伦·海斯

9
我不推荐这个。特别是ISNULL部分。如果EF检测到两个相同的PK,则它可能不会呈现唯一记录,而是返回一个共享对象。这以前发生在我身上。
Todd

@Todd-如果MyPrimaryID是NOT NULL列怎么可能发生?
JoeCool 2014年

@JoeCool,仅仅因为它不是NULL并不意味着它是唯一的。我赞成“此解决方案...”,因为无论使用何种上下文,都可以确保其唯一性。尽管现在正在考虑,但是如果删除一条记录,则将有效地更改以下记录的“ PK”。
Todd 2014年

1
-1,因为这不能回答如何配置Entity Framework / C#代码来处理如何映射到缺少身份种子的表。(出于某种原因)某些第三方软件是以这种方式编写的。
安德鲁·格雷

27

如果我想使用它们并修改数据,是否必须将PK添加到这些表中,或者是否有解决方法以便不必这样做?

对于那些遇到此问题并正在使用Entity Framework Core的用户,您不再需要在这些表中添加PK或进行任何变通方法。从EF Core 2.1开始,我们有了新功能“ 查询类型”

查询类型必须用于:

  • 用作临时FromSql()查询的返回类型。
  • 映射到数据库视图。
  • 映射到未定义主键的表。
  • 映射到模型中定义的查询。

因此,在您的DbContext中,只需添加以下type属性,DbQuery<T>而不是DbSet<T>像下面这样。假设您的表名是MyTable

public DbQuery<MyTable> MyTables { get; set; }

1
如果您使用的是EF Core,则为最佳答案!
杰(Jay)

17

复合键也可以使用Entity Framework Fluent API完成

public class MyModelConfiguration : EntityTypeConfiguration<MyModel>
{
     public MyModelConfiguration()
     {
        ToTable("MY_MODEL_TABLE");
        HasKey(x => new { x.SourceId, x.StartDate, x.EndDate, x.GmsDate });
        ...
     }
}

在这种“手动映射”情况下,我发现指定显示的自定义键是有效的。另外,如果您没有复合键的好处(如本答案所示),则可以为那些非自然或复合但可以依赖的特殊键标记一个modelBuilder.Entity<T>()链式呼叫.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)(通常,无论如何)。–
安德鲁·格雷

5

就我而言,我必须将实体映射到没有主键的View。而且,不允许我修改此视图。幸运的是,该视图具有一列,该列是唯一的字符串。我的解决方案是将此列标记为主键:

[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[StringLength(255)]
public string UserSID { get; set; }

被骗的EF。工作完美,没有人注意到... :)


UserID如果使用“代码优先”方法,它将创建该列。
Deepak Sharma 2014年

1
您尚未欺骗EF。您刚刚命令关闭Itentity功能。基本上,如果我是正确的,它仍将UserID为您创建列为PK,但是UserID当您创建新记录时(默认情况下),它将不会自动增加。另外,您仍然需要在中保留不同的值UserID
Celdor

1
@Celdor UserSID是一个字符串,它将永远不会“自动增加”。如果它是整数标识列,则数据库将在插入时(而不是实体框架)对其进行递增。
reggaeguitar '17

4

EF在数据库上不需要主键。如果确实如此,则无法将实体绑定到视图。

您可以修改SSDL(和CSDL)以将唯一字段指定为主键。如果您没有一个独特的领域,那么我相信您会感到很沮丧。但是您确实应该有一个唯一的字段(和PK),否则以后您将遇到问题。

埃里克


这样可以避免ISNULL hack。但是根据情况,可能还需要其他答案-例如,我感觉EF中的PK不支持某些数据类型。
2014年

3

有时,没有用的身份密钥毫无意义。我发现如果没有使用ID,为什么要添加它?但是,Entity并不是很宽容,因此最好添加ID字段。即使在不使用它的情况下,它也比处理Entity有关丢失的身份密钥的连续错误更好。


3

此解决方案有效

即使您没有PK,也无需手动映射。您只需要告诉EF您的其中一列是索引,并且索引列不可为空。

为此,您可以使用isNull函数将行号添加到视图中,如下所示

select 
    ISNULL(ROW_NUMBER() OVER (ORDER BY xxx), - 9999) AS id
from a

ISNULL(id, number) 是这里的关键点,因为它告诉EF此列可以是主键


2
我不会建议ISNULL部分。如果EF检测到两个相同的PK,则它可能不会呈现唯一记录,而是返回一个共享对象。这以前发生在我身上。
2013年

1
您必须使用isnull,否则EF不会相信它不可为空。
Archlight 2014年

2

如果您确实没有PK,以上答案是正确的。

但是,如果有一个,但是只是在DB中没有用索引指定,并且您不能更改DB(是的,我在Dilbert的世界中工作),则可以手动将字段映射为键。


2

这只是@Erick T的答案的补充。如果没有具有唯一值的单列,则解决方法是使用组合键,如下所示:

[Key]
[Column("LAST_NAME", Order = 1)]
public string LastName { get; set; }

[Key]
[Column("FIRST_NAME", Order = 2)]
public string FirstName { get; set; }

同样,这只是一种解决方法。真正的解决方案是修复数据模型。


2

也许这来得晚了...但是...

如果表没有主键,则只有少数情况需要分析才能使EF正常工作。规则是:EF将使用具有主键的表/类。这就是跟踪的方式...

假设您的表是1.记录是唯一的:唯一性是由单个外键列构成的:2.记录是唯一的:唯一性是由多个列的组合构成的。3.记录不是唯一的(大部分情况下*)。

对于场景1和场景2,您可以将以下行添加到DbContext模块的OnModelCreating方法中:modelBuilder.Entity()。HasKey(x => new {x.column_a,x.column_b}); //尽可能多的列来使记录唯一。

对于方案#3,您在研究表格后仍可以使用上述解决方案(#1 +#2)(*使得所有记录都保持唯一)。如果必须包含ALL列以使所有记录唯一,则可能需要向表中添加主键列。如果此表来自第三方供应商,则将此表克隆到本地数据库(隔夜或需要的时间),并通过克隆脚本任意添加主键列。


1
  1. 更改表结构并添加主列。更新模型
  2. 在XML编辑器中修改.EDMX文件,然后尝试为此标签添加新列到此特定表下(将不起作用)
  3. 我将创建一个包含所有现有列的复合键(WORKED),而不是为退出表创建新的主列。

实体框架:将没有主键的数据表添加到实体模型。


我在EF 4.0上尝试了复合键方法,但这种方法无效。
拉尔夫·威戈斯

这种方法对我来说非常有效,有时“使用”旧系统可能会很痛苦……
pinmonkeyiii 2014年

1

更新为@CodeNotFound的答案。

在EF Core 3.0 DbQuery<T>中,建议不要使用Keyless实体类型,而该实体类型应该做同样的事情。这些是使用ModelBuilder HasNoKey()方法配置的。在您的DbContext类中,执行此操作

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<YourEntityType>(eb =>
        {
            eb.HasNoKey();
        });

}

但是有一些限制,特别是:

  • 永远不会跟踪DbContext中的更改,因此永远不会在数据库上插入,更新或删除。
  • 仅支持导航映射功能的子集,特别是:
    • 他们可能永远不会成为恋爱关系的主要目的。
    • 他们可能没有到拥有实体的导航
    • 它们只能包含指向常规实体的参考导航属性。
    • 实体不能包含无键实体类型的导航属性。

这意味着对于

如果我想使用它们并修改数据,是否必须将PK添加到这些表中,或者是否有解决方法以便不必这样做?

您不能以这种方式修改数据-但是您可以读取。但是,人们可以设想使用另一种方式(例如ADO.NET,Dapper)来修改数据-这在您很少需要执行非读取操作而仍然希望在大多数情况下仍然坚持使用EF Core的情况下是一种解决方案。

另外,如果您确实需要/想要使用堆(无键)表,请考虑放弃EF并使用另一种方式与数据库进行通讯。


0

从实际的角度来看,每个表(甚至是非规范化表,例如仓库表)都应具有主键。否则,它至少应具有唯一的,不可为空的索引。

如果没有某种唯一键,重复的记录就会(并且将会)出现在表中,这对于ORM层以及对数据的基本理解都非常麻烦。具有重复记录的表可能是设计不良的征兆。

该表至少应至少具有一个标识列。添加自动生成的ID列在SQL Server中大约需要2分钟,在Oracle中大约需要5分钟。付出额外的努力,可以避免很多很多问题。


我的应用程序位于数据仓库环境中(使用Oracle),您说服我经过5分钟来添加索引。实际上只需要5分钟(如果您需要查找或修改ETL,则只需要5分钟)。
特伦特

0

我们也遇到了这个问题,当我们有一个包含空值的列时,重要的是我们有一个不包含空值的从属列,并且这两个列的组合是唯一的。

因此,引用Pratap Reddy的答复,它对我们来说很好。




By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.