c# - 违反 PRIMARY KEY 约束 : Cannot insert duplicate key in object

标签 c# linq asp.net-mvc-4 linq-to-xml automapper

当我想要持久化一个复杂的模型时,我得到了这个错误。我想我知道它来自哪里,但我不知道如何解决它。我正在导入一些提要并自动创建对象,包括子项(多对多)。

{"Violation of PRIMARY KEY constraint 'PK_dbo.Parent'. Cannot insert duplicate key in object 'dbo.Parent'. The duplicate key value is (291).\r\nThe statement has been terminated."}

错误不言自明,但如何预防呢? :)

触发它的代码

var parser = new SchoolFeedReader();
var update = parser.GetAll();
var students = Mapper.Map<List<StudentDTO>, List<Student>>(update);
using (var db = new SchoolContext())
{
    // I'm updating every night, so clean out the database before import
    db.Database.ExecuteSqlCommand("DELETE FROM Student");
    db.Database.ExecuteSqlCommand("DELETE FROM Parent");
    db.Database.ExecuteSqlCommand("DELETE FROM Subject");
    db.Database.ExecuteSqlCommand("DELETE FROM StudentParent");
    db.Database.ExecuteSqlCommand("DELETE FROM StudentSubject");

    students.ForEach(s => db.Students.Add(s));
    db.SaveChanges(); // Triggers the Exception
}

TL;DR

对于一个学校项目,我需要将 3 个 XML 提要导入数据库。

  • Students.xml
  • Parents.xml
  • 主题.xml

在 Students.xml 中,我遇到了一个设计缺陷:固定数量 (3) 的可能父对象。

<student>
    <StudentId>100</StudentId>
    <Name>John Doe</Name>
    <Location>Main Street</Location>
    <Parent1>1002</Parent1>
    <Parent2>1002</Parent2>
    <Parent3/>
</student>
(... more students)

在 Parents.xml 中,事情更简单。

<parent>
    <ParentId>1102</ParentId>
    <Name>Dad Doe</Name>
    <Email>dad@doe.com</Email>
</parent>
(... more parents)

而Subjects.xml也很简单。

<subject>
    <StudentId>100</StudentId>
    <Name>English</Name>
</subject>
(... more subjects)

模型

所以我创建了 3 个模型,包括 DTO。

public class Student
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public long StudentId { get; set; }
    public string Name { get; set; }
    public string Location { get; set; }

    [InverseProperty("Students")]
    public virtual ICollection<Parent> Parents { get; set; }
    public virtual ICollection<Subject> Subjects { get; set; } 
}

public class StudentDTO
{
    public long StudentId { get; set; }
    public string Name { get; set; }
    public string Location { get; set; }

    public List<ParentDTO> Parents { get; set; }
    public List<SubjectDTO> Subjects { get; set; } 
}

public class Parent
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public long ParentId { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }

    [InverseProperty("Parents")]
    public virtual ICollection<Student> Students { get; set; } 
}

public class ParentDTO
{
    public long ParentId { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public List<StudentDTO> Students { get; set; }

    public ParentDTO()
    {
        Students = new List<StudentDTO>();
    }
}

public class Subject
{
    public long SubjectId { get; set; }
    public string Name { get; set; }
    public virtual List<Student> Students { get; set; }
}

public class SubjectDTO
{
    public string Name { get; set; }
    public List<StudentDTO> Students { get; set; }

    public SubjectDTO()
    {
        Students = new List<StudentDTO>();
    }
}

从 XML 到 DTO

Importer 类有这个巨大的 LINQ 查询,可以一次性获得我需要的一切。

var query = from student in _xStudents.Descendants("Student")
            select new StudentDTO
            {
                StudentId = (long)student.Element("StudentId"),
                Name = (String)student.Element("Name"),
                Subjects = (
                     from subject in _xSubjects.Descendants("Subject").DefaultIfEmpty()
                     where (String)student.Element("StudentId") == (String)subject.Element("StudentId")
                     select new SubjectDTO
                     {
                         Name = (String)subject.Element("Name")
                     }
                ).ToList(),
                Parents = (
                    from parent in _xParents.Descendants("Parent").DefaultIfEmpty()
                    group parent by (String)parent.Element("ParentId") into pg
                    where (String)student.Element("Parent1") == (String)pg.FirstOrDefault().Element("ParentId") ||
                          (String)student.Element("Parent2") == (String)pg.FirstOrDefault().Element("ParentId") ||
                          (String)student.Element("Parent3") == (String)pg.FirstOrDefault().Element("ParentId")

                    select new ParentDTO
                    {
                        ParentId = (long)pg.FirstOrDefault().Element("ParentId"),
                        Name = (String)pg.FirstOrDefault().Element("Name")
                    }
                ).ToList()
            };

效果很好,一些学生有 2 个 parent ,一些有 1 个,所以我的数据看起来不错。

问题

我的 Global.asax.cs 中有这些 AutoMappers:

Mapper.CreateMap<StudentDTO, Student>()
    .ForMember(dto => dto.Parents, opt => opt.MapFrom(x => x.Parents))
    .ForMember(dto => dto.Subjects, opt => opt.MapFrom(x => x.Subjects));
Mapper.CreateMap<ParentDTO, Parent>();
Mapper.CreateMap<SubjectDTO, Subject>();

但是当我开始导入时,我的 db.SaveChanges() 出现错误。它提示 Parent 模型上有重复的 ForeignKey。所以我在想:

it's a Many-to-Many relationship, so if John Doe's sister, Jane Doe, tries to insert the same Dad Doe, then it crashes

那么我怎样才能确保整个映射业务对象集对每个实体只有 1 个引用?如何删除重复的爸爸妈妈?我可能也想为 Subject 做这件事。

最佳答案

如果两个或更多student in _xStudents.Descendants("Student")引用相同的父项(通过 id),然后创建两个或更多 ParentDTO s 具有相同的 ID,因此您随后尝试在 Importer 中两次插入相同的主键类。

如果您只是预处理您的 _xParents , 将它们转换为一个新的 ParentDTO 列表,这是 ParentId 唯一的然后你可以在你的 var query 中使用它获取对单个 ParentDTO 的引用引用给定 ParentId PK 的实例。

此代码示例不会对您的代码进行太多更改,因此您可以轻松地将其与原始代码相关联。但是请注意,您可能可以对此进行优化,并且您的 SubjectDTO 也会遇到同样的问题。如果您使用 SubjectDTO.Name,也请列出独一无二(我猜你应该如此)。

var parents = (from parent in _xParents.Descendants("Parent").DefaultIfEmpty()
              group parent by (String)parent.Element("ParentId") into pg
              select new ParentDTO
              {
                  ParentId = (long)pg.FirstOrDefault().Element("ParentId"),
                  Name = (String)pg.FirstOrDefault().Element("Name")
              // you might want to not use ToList here and let parents be an IEnumerable instead
              }).ToList(); 

var query = from student in _xStudents.Descendants("Student")
            select new StudentDTO
            {
                StudentId = (long)student.Element("StudentId"),
                Name = (String)student.Element("Name"),
                Subjects = (
                        from subject in _xSubjects.Descendants("Subject").DefaultIfEmpty()
                        where (String)student.Element("StudentId") == (String)subject.Element("StudentId")
                        select new SubjectDTO
                        {
                            Name = (String)subject.Element("Name")
                        }
                ).ToList(),
                Parents = (
                    from parent in parents
                    // Calling ToString each time is not fantastic
                    where (String)student.Element("Parent1") == parent.ParentId.ToString() ||
                            (String)student.Element("Parent2") == parent.ParentId.ToString() ||
                            (String)student.Element("Parent3") == parent.ParentId.ToString()

                    select parent
                ).ToList()
            };

关于c# - 违反 PRIMARY KEY 约束 : Cannot insert duplicate key in object,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16672309/

相关文章:

c# - 允许在 SQL 事务期间读取?

c# - 如何为 ICollection<T> 定义扩展方法,其中 T : IMyInterface without Specifying T in the Method Definition

c# - 如何使用linq在单一方法中创建聚合函数?

javascript - jqgrid创建后如何应用列模板

c# - MVC : Binding CKEditor textarea value to the model

javascript - 在创建不显示错误消息的部分 View 的集合上使用 FluentValidation

c# - 为特定用户更新数据库

c# - 无法使用接口(interface)反序列化类型

c# - 嵌套列表,我如何使用 lambda 表达式来做到这一点?

c# - 将 IEnumerable 转换为每个值具有多个键的查找