升级到Entity Framework 4.3.1后出现未处理的exception

错误:

未处理的exception:System.Data.SqlClient.SqlException:操作失败,因为表’PrivateMakeUpLessons’上已存在名称为“IX_ID”的索引或统计信息。

模型(简化,在单独的测试项目中构建以进行调试):

public abstract class Lesson { public Guid ID { get; set; } public string Room { get; set; } public TimeSpan Time { get; set; } public int Duration { get; set; } } public abstract class RecurringLesson : Lesson { public int DayOfWeek { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } public string Frequency { get; set; } } public class PrivateLesson : RecurringLesson { public string Student { get; set; } public string Teacher { get; set; } public virtual ICollection Cancellations { get; set; } } public class Cancellation { public Guid ID { get; set; } public DateTime Date { get; set; } public virtual PrivateLesson Lesson { get; set; } public virtual MakeUpLesson MakeUpLesson { get; set; } } public class MakeUpLesson : Lesson { public DateTime Date { get; set; } public string Teacher { get; set; } public virtual Cancellation Cancellation { get; set; } } 

组态:

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity().ToTable("Lessons"); modelBuilder.Entity().ToTable("RecurringLessons"); modelBuilder.Entity().ToTable("PrivateLessons"); modelBuilder.Entity().ToTable("PrivateMakeUpLessons"); modelBuilder.Entity() .HasOptional(x => x.MakeUpLesson) .WithRequired(x => x.Cancellation); base.OnModelCreating(modelBuilder); } 

备注

这在EF 4.2中运行良好。 我的模特有问题吗? 实际模型要复杂得多,这就是我将所有类抽象出来的原因。 此外,我正在对现有数据库工作,所以我需要使用Table-Per-Typeinheritance。

如果我将CancellationPrivateMakeUpLesson的关系从1更改为0..1到0..1到0..1就可以了。 这是不可取的,因为没有Cancellation就不能拥有PrivateMakeUpLesson

此外,如果我使PrivateMakeUpLesson不从Lessoninheritance,那么它也可以工作,但它是一个教训,需要保持现有的业务逻辑。

我很感激任何指导。 谢谢!

编辑

开始赏金。 关于代码的索引生成,我找不到关于EF 4.2和EF 4.3之间发生什么变化的任何文档。 很明显,EF 4.3正在创建更多索引并且命名方案已经改变,但我想知道EF中是否存在错误,或者我的模型或流畅的API配置是否存在根本性错误。

从EF 4.3开始,在数据库创建期间为freign键列添加索引。 有一个错误可能导致索引被多次创建。 这将在未来的EF版本中修复。

在此之前,您可以使用迁移而不是数据库初始化程序(或Database.Create()方法)创建数据库来解决此问题。

生成初始迁移后,您需要删除对Index()的冗余调用。

 CreateTable( "dbo.PrivateMakeUpLessons", c => new { ID = c.Guid(nullable: false), ... }) .PrimaryKey(t => t.ID) .ForeignKey("dbo.Lessons", t => t.ID) .ForeignKey("dbo.Cancellations", t => t.ID) .Index(t => t.ID) .Index(t => t.ID); // <-- Remove this 

要在运行时继续创建数据库,可以使用MigrateDatabaseToLatestVersion初始化程序。

在我看来,这显然是一个错误。

问题始于EF根据其创建索引IX_ID 。 如果您将模型剥离到以下…

 public abstract class Lesson { public Guid ID { get; set; } } public class RecurringLesson : Lesson { } public class MyContext : DbContext { public DbSet Lessons { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity().ToTable("RecurringLessons"); } } 

…并且让EF创建数据库模式,您可以获得TPTinheritance映射所需的两个表LessonsRecurringLessons 。 但我想知道为什么它为表RecurringLessons创建两个索引:

  • 使用索引列ID索引PK_RecurringLessons (群集,唯一)
  • 索引列ID再次索引IX_ID (非群集,不唯一)

我不知道数据库在同一列上有第二个索引是否有任何好处。 但是根据我的理解,1)在PK聚簇索引中已经覆盖的同一列上创建索引,以及2)在作为主键的列上创建非唯一索引并因此必然独特。

此外,由于一对一关系,EF尝试在该关联的依赖表上创建索引,该关联是PrivateMakeUpLessons 。 (它是依赖(而不是主体)因为实体MakeUpLesson 需要 Cancellation 。)

ID是此关联中的外键(同时是主键,因为一对一关系始终是Entity Framework中的共享主键关联)。 EF显然总是在关系的外键上创建索引。 但对于一对多关系,这不是问题,因为FK列与PK列不同。 对于一对一关系不是这样:FK和PK是相同的(即ID ),因此EF尝试为这种一对一关系创建索引IX_ID ,该关系由于TPTinheritance映射已经存在(其中从数据库的角度来看,这也导致了一对一的关系。

与上述相同的考虑适用于此:表PrivateMakeUpLessons在列ID上具有聚簇PK索引。 为什么在同一列上需要第二个索引IX_ID

此外,EF似乎没有检查它是否已经想要为TPTinheritance创建名为IX_ID的索引,最终导致在发送DDL以创建数据库模式时数据库中的exception。

EF 4.2(以及之前)根本没有创建任何索引(PK索引除外),这是在EF 4.3中引入的,尤其是FK列的索引。

我没有找到解决方法。 在最坏的情况下,您必须手动创建数据库模式,并避免EF尝试创建它(=禁用数据库初始化)。 在最好的情况下,有一种方法可以禁用自动FK索引创建,但我不知道是否可能。

您可以在此处提交错误报告: http : //connect.microsoft.com/VisualStudio

或者EF开发团队的某个人会在这里看到您的问题并提供解决方案。

一段时间后,我的代码中出现了类似的错误。 尝试将取消列表放在Lesson类中。 这就解决了我的问题。

下面我描述了两个可能出错的场景。 请通过单击我提供的链接深入阅读,以了解有关我的解释的更多信息。


第一
LessonRecurringLessonabstract类(因此您希望将它作为基类 )。
您正在创建LessonRecurringLesson实体的表,这将导致每个层次结构的简要描述;简介
创建基表的类将产生一个包含所有inheritance表的列的大表。 因此, PrivateLessonMakeUpLesson和所有其他inheritance实体的所有属性都将存储在Lessons表中。 EF还将添加一个Discriminator列。 此列的值默认为持久类名称(如“PrivateLesson”或“MakeUpLesson”),只有与该特定实体匹配的列(与Discriminator值匹配)才会在该特定行中使用。


您还要映射inheritance的类,如PrivateLessonMakeUpLesson 。 这将强制EF使用每个类型结构,这导致每个类一个表。 这可能会导致您现在面临的冲突。


第二
您的示例显示您具有一对一关系( Cancellation -> MakeUpLesson )和一对多关系( Cancellation -> PrivateLesson ),因为PrivateLessonMakeUpLesson都是(间接)inheritance自Lesson并结合第一个描述场景可能会导致问题,因为它会在每个实体的数据库中导致2个外键关系。 (一个使用Table每个层次结构,一个使用Table per Type结构)。

此帖也可以帮助您定义正确的一对一定义。


请通过执行以下步骤进行validation:
我假设您有自己的测试环境,因此您可以创建新的测试数据库

1.通过向该类注释掉所有属性来删除与Cancellation的关系:

 public class PrivateLesson : RecurringLesson { public string Student { get; set; } public string Teacher { get; set; } //public virtual ICollection Cancellations { get; set; } } public class Cancellation { public Guid ID { get; set; } public DateTime Date { get; set; } //public virtual PrivateLesson Lesson { get; set; } //public virtual MakeUpLesson MakeUpLesson { get; set; } } public class MakeUpLesson : Lesson { public DateTime Date { get; set; } public string Teacher { get; set; } //public virtual Cancellation Cancellation { get; set; } } 

并删除配置:

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity().ToTable("Lessons"); modelBuilder.Entity().ToTable("RecurringLessons"); modelBuilder.Entity().ToTable("PrivateLessons"); modelBuilder.Entity().ToTable("PrivateMakeUpLessons"); //modelBuilder.Entity() // .HasOptional(x => x.MakeUpLesson) // .WithRequired(x => x.Cancellation); base.OnModelCreating(modelBuilder); } 

2.创建一个新的空数据库
3.让EF在这个空数据库中为您生成表结构。
4.validation第一个方案。 如果确实如此,则需要首先使用每个层次结构的每个类型结构来修复。 可能你想使用每个层次结构的表,因为(如果我理解你的问题),已经有一个生产环境。

当我的项目从EF 6.0.2更新到EF 6.1.1时,我遇到了这样的问题,然后回到6.0.2,在旧版本返回后,错误消失了