违反PRIMARY KEY约束:无法在对象中插入重复键

时间:2013-05-21 14:16:15

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

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

  

{“违反PRIMARY KEY约束'PK_dbo.Parent'。无法插入   对象'dbo.Parent'中的重复键。重复的键值是   (291)。\ r \ n声明已经终止。“}

错误不言而喻,但如何预防呢? :)

触发它的代码

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 Feeds导入数据库。

  • Students.xml
  • Parents.xml
  • Subjects.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个父母,有些得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。所以我在想:

  

这是一个多对多关系,所以如果John Doe的姐姐Jane Doe试图插入同一个Dad Doe,那么它会崩溃

那么如何确保整个Mapped Business Objects集合只有1个引用每个实体;如何删除重复的爸爸和妈妈的?我可能也想为主题这样做。

3 个答案:

答案 0 :(得分:2)

如果两个或多个student in _xStudents.Descendants("Student")引用相同的父项(按ID),则您创建两个或多个具有相同ID的ParentDTO,因此您尝试两次插入相同的主键在Importer课程中。

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

此代码示例不会更改您的代码,以便您可以轻松地将其与原始代码相关联。但是请注意,您可以对此进行优化,如果您使用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()
            };

答案 1 :(得分:1)

真正的问题在于制图。 Mapper将相同的父级添加两次,因此它的新实体,它处于已添加状态。后来dbContext将其视为新记录,并尝试插入。 我看到三个选项:

  1. 将StudentDTO.ParentDTO替换为StudentDTO.IDParentDTO
  2. 添加StudentDTO.IDParentDTO并忽略映射中的StudentDTO.ParentDTO
  3. 玩地图。有很多功能,但你只需要找到它们。查看this问题

答案 2 :(得分:0)

我收到了用户自定义表类型的错误。在构建数据关系时,我有时会多次拉出相同的记录。如果合适,请在声明 PRIMARY KEY 时转动 ON IGNORE_DUP_KEY

Microsoft index_option (w / IGNORE_DUP_KEY)

示例:

CREATE TYPE [dbo].[udt_Promotion] AS TABLE(
  [PromotionID] [int] NOT NULL PRIMARY KEY CLUSTERED WITH (IGNORE_DUP_KEY = ON),
  ...
)