【问题标题】:Unhandled Exception after Upgrading to Entity Framework 4.3.1升级到实体框架 4.3.1 后未处理的异常
【发布时间】:2012-05-04 02:31:47
【问题描述】:

错误:

未处理的异常: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 继承。

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

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

我将不胜感激任何指导。谢谢!

编辑

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

【问题讨论】:

  • 您提到您正在使用现有数据库,但此错误看起来更像 EF 正在尝试创建和索引 - 只有当 EF 修改数据库架构时才会发生这种情况。你在使用 EF 迁移吗?
  • @LadislavMrnka 不,我仍在手动编写迁移脚本。我刚刚提到我已经有一个数据库在生产中,所以我不想更改继承映射方法。
  • 我同意这听起来像是在尝试重新创建数据库,因为您使用的是自定义迁移脚本,因此您是否禁用了数据库的重新创建约定?

标签: c# entity-framework ef-code-first


【解决方案1】:

从 EF 4.3 开始,在数据库创建期间为外键列添加索引。有一个错误可能导致索引被多次创建。这将在未来的 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 初始化程序。

【讨论】:

  • 感谢您的解决方法!该错误会在 EF 5.0 中修复吗?还是以后的版本?
  • 感谢您抽出宝贵时间研究此问题并发布解决方法。我什至还没有看过 EF 迁移,所以我想这是花一些时间来检查它的一个很好的理由。
  • @Slauma,目前看来这将在 EF 5.0.0 的 RTM 版本中得到修复
  • @jerone,感谢您指出这一点!我们修复了一个类似的问题,即创建了重复索引,但这个特定情况被遗漏了。我已经提交了Work Item 1035 以确保这个也得到修复。
  • 刚刚在工作项 1035 中添加了一条注释,表明如果使用子类型和 TPH 继承,它也会产生重复的外键。
【解决方案2】:

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

问题始于观察到 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
  • 再次使用索引列ID 索引IX_ID(非聚集,非唯一)

我不知道数据库在同一列上有第二个索引是否有任何好处。但据我了解,1)在 同一列 上创建一个索引已包含在 PK 聚集索引中,以及 2)创建一个 非唯一 strong> 列上的索引,该列是主键,因此必须是唯一的。

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

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

此处适用与上述相同的注意事项:表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 开发团队的某个人会在这里看到您的问题并提供解决方案。

【讨论】:

  • 感谢您对此的关注。在查看 EF 4.2 与 4.3 中生成的索引时,我得出了相同的结论,但我不确定我是否做错了什么或者是否有解决方法。我会报告这个错误,看看是否希望 ADO.NET 团队的某个人可以跟进这个问题。再次感谢!
【解决方案3】:

不久前,我的代码中出现了一个与此非常相似的错误。尝试将取消列表放入课程课程中。这就是解决我的问题的方法。

【讨论】:

  • 不幸的是,这对我来说不是一个解决方案,因为不同类型的课程实际上有不同类型的取消。为了更容易理解,我在这里简化了模型。
【解决方案4】:

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


首先
LessonRecurringLessonabstract 类(因此您希望将其作为基类)。
您正在创建一个包含 LessonRecurringLesson 实体的表,这将导致 Table per hierarchy 结构。 简要说明
创建 基表 的类将产生一个包含所有继承表的列的大表。因此PrivateLessonMakeUpLesson 和所有其他继承实体的所有属性都将存储在Lessons 表中。 EF 还将添加一个Discriminator 列。此列的值默认为持久类名称(如“PrivateLesson”或“MakeUpLesson”),只有与该特定实体匹配的列(匹配鉴别器值)才会在该特定行中使用。

但是
您还映射了继承的类,例如 PrivateLessonMakeUpLesson。这将强制 EF 使用 Table per Type 结构,这会导致每个类一个表。这可能会导致您现在面临的冲突。


第二
您的示例显示您具有一对一关系 (Cancellation -&gt; MakeUpLesson) 和一对多关系 (Cancellation -&gt; PrivateLesson),因为 PrivateLessonMakeUpLesson 都(间接)继承自 Lesson 组合first 描述的场景可能会导致问题,因为它会导致每个实体在数据库中产生 2 个外键关系。 (一个使用 Table per hierarchy 结构,一个使用 Table per Type 结构)。

另外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 结构或 Table per Type 结构来解决此问题。可能您想使用 Table per hierarchy 结构,因为(如果我很好地理解您的问题)已经有一个生产环境。

【讨论】:

  • "这将强制 EF 使用每个类型的表结构,这会导致每个类一个表。这可能会导致您现在面临的冲突。" -- 我不明白使用 TPT 是如何导致问题的。我在我的问题中提到这是我想使用的继承策略。它在 EF 4.2 中运行良好,所以我不明白为什么将 TPT 与我的域模型一起使用是一个糟糕的选择。
  • TPT 不是一个糟糕的选择,但如果 EF 同时使用这两种结构,你会遇到冲突。例如,如果您有class Side : Base。如果 EF 使用 TPH,您将获得一张表 [Base],其中包含 Base 和 Side 的所有列。如果您同时拥有 TPT 结构,那么您在 [Side] 表中列出了 side 的所有列。我更喜欢你已经使用过的 TPT。但是 EF 支持两种在数据库中保存继承实体的方式
【解决方案5】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-22
    • 2013-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多