错误:
未处理的异常: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<Cancellation> 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<Lesson>().ToTable("Lessons");
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
modelBuilder.Entity<PrivateLesson>().ToTable("PrivateLessons");
modelBuilder.Entity<MakeUpLesson>().ToTable("PrivateMakeUpLessons");
modelBuilder.Entity<Cancellation>()
.HasOptional(x => x.MakeUpLesson)
.WithRequired(x => x.Cancellation);
base.OnModelCreating(modelBuilder);
}
备注:
这在EF 4.2中运行良好。我的模型有问题吗?实际模型要复杂得多,这就是我将所有类抽象出来的原因。另外,我正在对现有数据库进行处理,因此我需要使用Table-Per-Type继承。
如果我将Cancellation
与PrivateMakeUpLesson
的关系从1更改为0..1到0..1更改为0..1它可以正常工作。这是不受欢迎的,因为如果没有PrivateMakeUpLesson
,您就无法拥有Cancellation
。
另外,如果我PrivateMakeUpLesson
不继承Lesson
,那么它也可以工作,但这是一个教训,需要保持现有业务逻辑。
我很感激任何指导。谢谢!
修改:
开始赏金。关于代码的索引生成,我找不到关于EF 4.2和EF 4.3之间发生什么变化的任何文档。很明显,EF 4.3正在创建更多索引并且命名方案已经改变,但我想知道EF中是否存在错误,或者我的模型或流畅的API配置是否存在根本性错误。
答案 0 :(得分:9)
从EF 4.3开始,在数据库创建期间为freign键列添加了索引。有一个错误可能导致索引被多次创建。这将在未来的EF版本中修复。
在此之前,您可以使用Migrations而不是数据库初始化程序(或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
初始化程序。
答案 1 :(得分:5)
在我看来,这显然是一个错误。
问题始于EF根据其创建索引IX_ID
。如果您将模型剥离到以下...
public abstract class Lesson
{
public Guid ID { get; set; }
}
public class RecurringLesson : Lesson
{
}
public class MyContext : DbContext
{
public DbSet<Lesson> Lessons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
}
}
...让EF创建数据库模式,您可以获得TPT继承映射所需的两个表Lessons
和RecurringLessons
。但我想知道为什么它为表RecurringLessons
创建两个索引:
PK_RecurringLessons
ID
(已群集,唯一)
IX_ID
的索引ID
(非群集,非唯一)我不知道数据库在同一列上有第二个索引是否有任何好处。但是根据我的理解,1)在PK聚簇索引中已经涵盖的相同列上创建索引,以及2)创建不唯一> 作为主键的列的索引,因此必然是唯一的。
此外,由于一对一关系,EF尝试在此关联的依赖关系表上创建索引PrivateMakeUpLessons
。 (这是依赖(而非主体)因为Cancellation
在实体MakeUpLesson
中必需。)
ID
是此关联中的外键(同时是主键,因为一对一关系始终是实体框架中的共享主键关联)。 EF显然总是在关系的外键上创建索引。但对于一对多关系,这不是问题,因为FK列与PK列不同。对于一对一关系不一样:FK和PK是相同的(即ID
),因此EF尝试为已存在的这种一对一关系创建索引IX_ID
由于TPT继承映射(从数据库角度看也导致了一对一的关系)。
与上述相同的考虑适用于此:表PrivateMakeUpLessons
在列ID
上具有聚簇PK索引。为什么在同一列上需要第二个索引IX_ID
?
此外,EF似乎没有检查它是否已经想要为TPT继承创建名为IX_ID
的索引,当发送DDL以创建数据库模式时,最终导致数据库中的异常
EF 4.2(以及之前)根本没有创建任何索引(PK索引除外),这是在EF 4.3中引入的,尤其是FK列的索引。
我没有找到解决方法。在最坏的情况下,您必须手动创建数据库模式,并避免EF尝试创建它(=禁用数据库初始化)。在最好的情况下,有一种方法可以禁用自动FK索引创建,但我不知道是否可能。
您可以在此处提交错误报告:http://connect.microsoft.com/VisualStudio
或者EF开发团队的某个人会在这里看到您的问题并提供解决方案。
答案 2 :(得分:2)
我的代码中有一个非常类似的错误。尝试将取消列表放在Lesson类中。这就解决了我的问题。
答案 3 :(得分:2)
下面我将介绍两种可能出错的情景。请通过单击我提供的链接深入阅读,以了解有关我的解释的更多信息。
<强>第一强>
Lesson
和RecurringLesson
是abstract
个类(因此您希望将其作为基类)。
您正在创建Lesson
和RecurringLesson
实体的表格,这将导致 Table per hierarchy structure。
简要说明
创建基表的类将生成一个包含所有继承表的列的大表。因此,PrivateLesson
,MakeUpLesson
和所有其他继承实体的所有属性都将存储在Lessons
表中。 EF还会添加Discriminator
列。此列的值默认为持久类名称(如“PrivateLesson”或“MakeUpLesson”),只有与该特定实体匹配的列(与Discriminator值匹配)才会在该特定行中使用。
<强> BUT 强>
您还要映射继承的类,如PrivateLesson
和MakeUpLesson
。这将强制EF使用 Table per Type structure,这会导致每个类一个表。这可能会导致您现在面临的冲突。
<强>第二强>
您的示例显示您具有一对一关系(Cancellation -> MakeUpLesson
)和一对多关系(Cancellation -> PrivateLesson
),因为PrivateLesson
和MakeUpLesson
都是(间接的) )继承自Lesson
并结合第一个描述的场景可能会导致问题,因为它会在每个实体的数据库中产生2个外键关系。 (一个使用每个层次结构的表结构,另一个使用每个类型的表结构)。
同样this post可以帮助您定义正确的一对一定义。
请执行以下步骤进行验证:
我假设您拥有自己的测试环境,因此您可以创建新的测试数据库
<强> 1 强>
通过将此类的所有属性注释掉来删除与Cancellation
的关系:
public class PrivateLesson : RecurringLesson
{
public string Student { get; set; }
public string Teacher { get; set; }
//public virtual ICollection<Cancellation> 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<Lesson>().ToTable("Lessons");
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
modelBuilder.Entity<PrivateLesson>().ToTable("PrivateLessons");
modelBuilder.Entity<MakeUpLesson>().ToTable("PrivateMakeUpLessons");
//modelBuilder.Entity<Cancellation>()
// .HasOptional(x => x.MakeUpLesson)
// .WithRequired(x => x.Cancellation);
base.OnModelCreating(modelBuilder);
}
<强> 2 强>
创建一个新的空数据库
第3 强>
让EF在这个空数据库中为您生成表结构
的 4 强>
验证第一个方案。如果确实如此,则需要首先使用 Table per hierarchy structure或 Table per Type structure来修复。您可能希望使用 Table per hierarchy structure,因为(如果我理解您的问题),已经存在生产环境。
答案 4 :(得分:1)
当我的项目从EF 6.0.2更新到EF 6.1.1时,我遇到了这样的问题,然后回到6.0.2,在旧版本返回后,错误消失了