实体框架迁移 - 引用完整性约束违规

4

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

我有一个使用Entity Framework、Repository和Unit of Work模式的MVC应用程序。最近我从使用网络数据库(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; }
}

每当我尝试更新学生的StudentStatus属性时,就会出现以下异常:
"The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property value(s) of 'StudentStatus.Id' on one end of a relationship do not match the property value(s) of 'Student.StudentStatusId' on the other end."
我尝试在保存更改之前将导航属性重置为null,但问题仍然存在。
student.StudentStatus = null;
student.StudentStatusId = 26;
_studentRepository.Update(student);
_unitOfWork.Commit();

我尝试检索特定的学生状态对象:

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

但是每次尝试执行DataContext.SaveChanges()时都会抛出相同的异常。

我可以更新StudentType(这是完全相同类型的关系和类),没有任何问题。

Update方法的实现:

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类和相应的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

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

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?这是否导致了潜在的问题?此外,为什么会有两个更新语句而不是一个?


请问您能展示一下 StudentStatus 类吗? - Corey Adler
我总是将[ForeignKey]属性添加到id属性上,并且在该属性的字符串中引用类名 - 如 - [ForeignKey(“StudentStatusType”)] public int StudentStatusTypeId {get; set;},并在其下面添加一个类型为StudentStatusType的虚属性。不知道这是否重要。 - Allmighty
我一直以来都是按照我贴出的代码那样做,直到现在都没有遇到任何问题。不过我会尝试你的建议,看看是否有所改善。 - Jerry
3个回答

1
对我来说,这种方法似乎更直观: 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; }
}

很不幸,我刚刚尝试在具有Id属性的属性上设置外键,但没有任何区别;仍然会得到相同的异常。 - Jerry
你正在重新生成这个(新)模型上的数据库,并在尝试选择和更新它们之前插入一些带有正确外键的虚拟数据吗? - Allmighty
我运行了Update-Database,但EF迁移没有检测到任何更改。我将清空表并重新填充以查看是否有所帮助。 - Jerry
重新填充了数据库,问题仍然存在 :( - Jerry
我会给你的答案点赞,因为它帮助我识别了数据最初种植方式存在的问题。我很快会发布我的答案。 - Jerry

0

EF 可能会因为 StudentStatus 上的导航属性与 Student 并没有关联而感到困惑。将以下行添加到 StudentStatus 类中应该就可以解决问题了。

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

0

在Adimeus的建议下,我不得不调查初始数据是如何被种植的。

相对于EF Migrations的Configuration.cs文件,StudentStatus而非StudentType被种植在我的服务层中 - 别问我为什么; 那是另一个处理迁移的开发人员。

现在是:

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 Migrations 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));

问题已经解决了!我现在可以更新学生状态了。不太明白为什么服务中的种子数据会导致这个问题。如果有人能解释一下,我会接受答案;否则我会在几天后接受自己的答案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接