Entity Framework 迁移 - 违反参照完整性约束

Entity Framework Migrations - A referential integrity constraint violation

我已经为此苦苦挣扎了一段时间,但在 Stack Overflow 上找不到解决方案。

我有一个 MVC 应用程序,它使用 Entity Framework 以及存储库和工作单元模式。我最近从使用网络数据库(Sql Server 2008 R2,问题似乎不存在)转移到本地数据库并开始使用 Entity Framework 迁移以便与其他开发人员合作而不是通过更改我们的模型来相互影响。

我的模型是这样的:

[Table("Student")]
public class Student
{
    [Key]
    public int Id { get; set; }

    <... other fields ...>

    [Required(ErrorMessage = "A student type is required")]
    public int StudentTypeId { get; set; }

    [Required(ErrorMessage = "A student status is required")]
    public int StudentStatusId { get; set; }

    [ForeignKey("StudentTypeId")]
    public virtual StudentType StudentType { get; set; }

    [ForeignKey("StudentStatusId")]
    public virtual StudentStatus StudentStatus { get; set; }
}

每次我尝试更新 Student 的 StudentStatus 属性 时,我都会收到以下异常:

"已成功提交对数据库的更改,但更新对象上下文时出错。 ObjectContext 可能处于不一致状态。内部异常消息:发生引用完整性约束冲突: 关系一端 'StudentStatus.Id' 的 属性 值与另一端 'Student.StudentStatusId' 的 属性 值不匹配。"

我已尝试在保存更改之前将导航 属性 重新设置为空。

student.StudentStatus = null;
student.StudentStatusId = 26;
_studentRepository.Update(student);
_unitOfWork.Commit();

我已经尝试检索特定的 StudentStatus 对象:

var studentStatus = _studentStatusRepository.GetById(26);
student.StudentStatusId = 26;
student.StudentStatus = studentStatus;
_studentRepository.Update(student);
_unitOfWork.Commit();

但每次尝试都会在 DataContext.SaveChanges() 上抛出相同的异常。

我可以毫无问题地更新 StudentType(实际上是同一种关系和相似 class)。

更新方法的实现:

public virtual void Update(T entity)
{
    try
    {
        DataContext.Entry(entity).State = EntityState.Modified;
    }
    catch (Exception exception)
    {
        throw new EntityException(string.Format("Failed to update entity '{0}'", typeof(T).Name), exception);
    }
}

我注意到,与我以前的网络数据库相比,EF Migrations 在数据库中创建了更多索引,但我后来手动删除了它们(我认为它们不一定是问题所在)。我尝试比较两个数据库中的关系,一切似乎都一样。

我知道如果我不提供更多信息可能很难指出问题所在,但还有什么可能导致此问题?

编辑(添加了 StudentStatus class 和对应的 StudentStatusType)

[Table("StudentStatus")]
public class StudentStatus
{
    [Key]
    public int Id { get; set; }

    [Required(ErrorMessage = "Student status name is required")]
    [MaxLength(50, ErrorMessage = "Student status name cannot be longer than 50 characters")]
    public string Name { get; set; }

    public int StudentStatusTypeId { get; set; }

    [ForeignKey("StudentStatusTypeId")]
    public virtual StudentStatusType StudentStatusType { get; set; }
}

[Table("StudentStatusType")]
public class StudentStatusType
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

编辑 2

我忘了提到我在存储库级别启用了数据库日志记录以查看 SQL 查询正在由 Entity Framework:

执行
DataContext.Database.Log = s => Debug.WriteLine(s); 

结果是:

UPDATE [dbo].[Student]
SET <... some parameters ...>, [StudentStatusId] = @10, <... some parameters ...>
WHERE ([Id] = @14)

UPDATE [dbo].[Student]
SET <... some parameters ...>, [StudentStatusId] = @10, <... some parameters ...>
WHERE ([Id] = @14)

<... some parameters ...>

-- @10: '25' (Type = Int32)

-- @10: '25' (Type = Int32)

<... some parameters ...>

-- Executing at 12/01/2015 12:30:41 +00:00

-- Executing at 12/01/2015 12:30:41 +00:00

-- Completed in 0 ms with result: 1

-- Completed in 0 ms with result: 1

那么为什么 EF 试图插入值 25,即使我明确指定它是 26?这是导致根本问题的原因吗?另外,为什么会有两个更新语句而不是一个?

对我来说,这种方法似乎更直观:

int StudentStatusType持有StudentStatusType.Id的值,所以应该标记为[ForeignKey]。 如果添加virtual StudentStatusType-属性,EntityFramework会自动绑定。

[Table("StudentStatus")]
public class StudentStatus
{
    [Key]
    public int Id { get; set; }

    [Required(ErrorMessage = "Student status name is required")]
    [MaxLength(50, ErrorMessage = "Student status name cannot be longer than 50 characters")]
    public string Name { get; set; }

    [ForeignKey("StudentStatusType")] 
    public int StudentStatusTypeId { get; set; }
    public virtual StudentStatusType StudentStatusType { get; set; }
}

[Table("StudentStatusType")]
public class StudentStatusType
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

EF 可能会因为 StudentStatus 上的导航 属性 与 Student 完全无关而感到困惑。将以下行添加到 StudentStatus class 应该可以解决问题。

public virtual ICollection<Student> Students { get; set; }

根据 Adimeus 的建议,我不得不调查初始数据的播种方式。

StudentStatus 而不是 StudentType 被植入我的服务层(而不是 EF 迁移的 Configuration.cs 文件)- 不要问我为什么;这是另一位处理迁移的开发人员。

原为:

if (!_studentStatusRepository.GetAll().Any())
{
    var newStudentStatus = _studentStatusTypeRepository.Get(x => x.Name == "New");
    var activeStudentStatus = _studentStatusTypeRepository.Get(x => x.Name == "Active");
    var deletedStudentStatus = _studentStatusTypeRepository.Get(x => x.Name == "Deleted");

    var studentStatuses = new List<StudentStatus>
    {
        new StudentStatus {Name = "New", StudentStatusType = newStudentStatus, StudentStatusTypeId = newStudentStatus.Id},
        new StudentStatus {Name = "Awaiting Approval", StudentStatusType = activeStudentStatus, StudentStatusTypeId = activeStudentStatus.Id},
        new StudentStatus {Name = "Approved", StudentStatusType = activeStudentStatus, StudentStatusTypeId = activeStudentStatus.Id},
        new StudentStatus {Name = "Deleted", StudentStatusType = deletedStudentStatus, StudentStatusTypeId = deletedStudentStatus.Id},
        new StudentStatus {Name = "Reinstated", StudentStatusType = deletedStudentStatus, StudentStatusTypeId = deletedStudentStatus.Id}
    };

    foreach (var studentStatus in studentStatuses.ToList())
    {
        StudentStatus status = studentStatus;
        var dbStudentStatus = _studentStatusRepository.Get(x => x.Name == status.Name);
        if (dbStudentStatus == null)
        {
            _studentStatusRepository.Add(studentStatus);
        }
    }
    _unitOfWork.Commit();
}

我已将播种移动到 EF 迁移 Configuration.cs 文件(我想它可能可以优化,但这是一个快速测试):

var studentStatuses = new List<StudentStatus>
{
    new StudentStatus
    {
        Name = "New",
        StudentStatusType = context.StudentStatusTypes.Single(x => x.Name == "New"),
        StudentStatusTypeId = context.StudentStatusTypes.Single(x => x.Name == "New").Id,
    },
    new StudentStatus
    {
        Name = "Awaiting Approval",
        StudentStatusType = context.StudentStatusTypes.Single(x => x.Name == "Active"),
        StudentStatusTypeId = context.StudentStatusTypes.Single(x => x.Name == "Active").Id,
    },
    new StudentStatus
    {
        Name = "Approved",
        StudentStatusType = context.StudentStatusTypes.Single(x => x.Name == "Active"),
        StudentStatusTypeId = context.StudentStatusTypes.Single(x => x.Name == "Active").Id,
    },
    new StudentStatus
    {
        Name = "Deleted",
        StudentStatusType = context.StudentStatusTypes.Single(x => x.Name == "Deleted"),
        StudentStatusTypeId = context.StudentStatusTypes.Single(x => x.Name == "Deleted").Id,
    },
    new StudentStatus
    {
        Name = "Reinstated",
        StudentStatusType = context.StudentStatusTypes.Single(x => x.Name == "Deleted"),
        StudentStatusTypeId = context.StudentStatusTypes.Single(x => x.Name == "Deleted").Id,
    }
};

studentStatuses.ForEach(x => context.StudentStatuses.AddOrUpdate(y => y.Name, x));

问题解决了!我现在可以更新 StudentStatus。不太明白为什么首先在服务中播种会导致问题。如果有人可以解释,我会接受答案;否则我会在几天后接受我的回答。