升级到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。
如果我将Cancellation
与PrivateMakeUpLesson
的关系从1更改为0..1到0..1到0..1就可以了。 这是不可取的,因为没有Cancellation
就不能拥有PrivateMakeUpLesson
。
此外,如果我使PrivateMakeUpLesson
不从Lesson
inheritance,那么它也可以工作,但它是一个教训,需要保持现有的业务逻辑。
我很感激任何指导。 谢谢!
编辑 :
开始赏金。 关于代码的索引生成,我找不到关于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映射所需的两个表Lessons
和RecurringLessons
。 但我想知道为什么它为表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类中。 这就解决了我的问题。
下面我描述了两个可能出错的场景。 请通过单击我提供的链接深入阅读,以了解有关我的解释的更多信息。
第一
Lesson
和RecurringLesson
是abstract
类(因此您希望将它作为基类 )。
您正在创建Lesson
和RecurringLesson
实体的表,这将导致每个层次结构的表 。 简要描述;简介
创建基表的类将产生一个包含所有inheritance表的列的大表。 因此, PrivateLesson
, MakeUpLesson
和所有其他inheritance实体的所有属性都将存储在Lessons
表中。 EF还将添加一个Discriminator
列。 此列的值默认为持久类名称(如“PrivateLesson”或“MakeUpLesson”),只有与该特定实体匹配的列(与Discriminator值匹配)才会在该特定行中使用。
但
您还要映射inheritance的类,如PrivateLesson
和MakeUpLesson
。 这将强制EF使用每个类型的表结构,这导致每个类一个表。 这可能会导致您现在面临的冲突。
第二
您的示例显示您具有一对一关系( Cancellation -> MakeUpLesson
)和一对多关系( Cancellation -> PrivateLesson
),因为PrivateLesson
和MakeUpLesson
都是(间接)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,在旧版本返回后,错误消失了