2012-05-04 72 views
10

错误:未处理的异常4.3.1

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

模型(简体,建立在调试一个单独的测试项目):

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继承。

如果我将CancellationPrivateMakeUpLesson的关系从1改变到0..1到0..1到0..1,它就起作用。这是不可取的,因为如果没有Cancellation,就不能有PrivateMakeUpLesson

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

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

编辑

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

+2

你的错误提到你正在使用现有的数据库,但是这个错误看起来更像是EF试图创建和索引 - 这只有在EF修改数据库模式时才会发生。你使用EF迁移? –

+0

@LadislavMrnka不,我仍然手动编写迁移脚本。我刚才提到我已经在生产中有一个数据库,所以我不想更改继承映射方法。 –

+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初始化。

+0

感谢您的解决方法!这个bug会在EF 5.0中解决吗?或更高版本? – Slauma

+0

感谢您花时间查看此内容并发布解决方法。我甚至没有看过EF迁移,所以我想这是一个很好的理由,需要一些时间来检查它。 –

+0

@Slauma,目前它看起来像这样会在EF 5.0.0的RTM版本中修复。 – bricelam

2

我在代码中得到了一个非常类似的错误。尝试将取消列表放入课程类中。这就是解决我的问题。

+0

不幸的是,这不是我的解决方案,因为对于不同类型的课程实际上有不同类型的取消。为了便于理解,我在此简化了模型。 –

2

下面我描述了两种情况,可能会出错。请点击我提供的链接以深入了解我的解释。


首先
LessonRecurringLessonabstract类(所以你想拥有它作为基类)。
您正在创建LessonRecurringLesson实体的表,这将导致Table per hierarchy structure简要说明
创建类的基表将导致包含所有继承表的列一个大表。所以PrivateLesson,MakeUpLesson和其他所有继承实体的所有属性将被存储在Lessons表中。 EF还将添加一个Discriminator列。此列的值默认为持久类名称(如“PrivateLesson”或“MakeUpLesson”),只有匹配该特定实体的列(与Discriminator值匹配)才会用于该特定行。


您也映射继承类,如PrivateLessonMakeUpLesson。这将强制EF使用Table per Type structure,这会导致每个类有一个表。这可能会导致你现在面临的冲突。



你的例子显示了你有一个一对一的关系(Cancellation -> MakeUpLesson)和一个一对多的关系(Cancellation -> PrivateLesson),因为PrivateLessonMakeUpLesson都(间接)从Lesson继承结合第一个描述的方案可能会导致问题,因为它会导致数据库中每个实体有2个外键关系。 (一个使用表格每层结构和一个使用表格每种类型结构)。

另外this post可以帮助您定义正确的一对一定义。


通过执行以下步骤请验证:
我假设你有你自己的测试环境,以便您可以创建新的测试数据库

被删除到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. 创建一个新的空数据库
让EF生成此空数据库为您的表结构。
4. 验证第一种情况。如果这是真的,则需要首先通过使用Table per hierarchy structureTable per Type structure来解决。可能你想使用Table per hierarchy structure,因为(如果我理解你的问题)已经有一个生产环境。

+0

“这将强制EF使用Table per Type结构,每个类都会生成一个表,这可能会导致您现在面临的冲突。” - 我不明白如何使用TPT导致问题。我在我的问题中提到,这是我想要使用的继承策略。它在EF 4.2中正常工作,所以我不明白为什么在我的域模型中使用TPT是一个糟糕的选择。 –

+0

TPT不是一个糟糕的选择,但如果EF同时使用两种结构,您将会发生冲突。例如,如果你有'班级:基地'。 如果EF使用TPH,您将得到一个表[Base],其中包含Base和Side的所有列。如果您同时拥有TPT结构,那么您还有** [列表]中列出的所有列。我更喜欢你已经使用过的TPT。但EF支持在数据库中保存被继承实体的两种方法 – hwcverwe

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创建数据库模式,你会得到两个表LessonsRecurringLessons人们所期待的TPT继承映射。但我不知道为什么它会创建指数表RecurringLessons

  • 指数PK_RecurringLessons(集群,独特的)和索引列ID
  • 指数IX_ID(不聚集,不唯一)与索引列ID再次

我不知道是否有任何好处的数据库在同一列上有第二个索引。但我的理解是没有意义1)建立在已经覆盖的PK聚集索引同一列的指数,以及2)创建一个列是主一个不是唯一的指数关键,因此必然是唯一的。

而且由于一对一的关系EF试图对因此关联是PrivateMakeUpLessons的的表中创建索引。 (这是相关的(而不是本金),因为Cancellation需要在实体MakeUpLesson

ID是在同一时间在该协会的外键(和主键,因为一到一个关系总是实体框架中的共享主键关联)。 EF显然总是在关系的外键上创建索引。但对于一对多关系,这不是问题,因为FK列与PK列不同。不那么对于一个对一relatonships:该FK和PK是相同的(即ID),因此EF试图创建一个索引IX_ID对于已经存在由于TPT继承映射该一对一的关系(其从数据库的角度来看也会导致一对一的关系)。

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

除EF似乎并没有检查它已经要创建一个名称IX_ID的指数为TPT继承,最终导致数据库异常时,DDL发送到创建数据库模式。

EF 4.2(之前)没有建立在所有的任何指标(除PK指数),这是在EF 4.3推出,尤其是指数是FK列。

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

你可以在这里提交错误报告:从EF开发团队http://connect.microsoft.com/VisualStudio

或者,也许有人会在这里看到你的问题,并提供解决方案。

+0

感谢您一直在寻找这一点。我在查看EF 4.2和4.3中生成的索引时得出了同样的结论,但我不确定自己是否做错了什么或者是否有解决方法。我会报告这个错误,看看ADO.NET团队中的某个人是否可以跟进这个问题。再次感谢! –

1

当我的项目是从EF 6.0.2更新至6.1.1 EF,我有这样的问题,然后再返回到6.0.2,旧版本的回归后,消失