【问题标题】:Violation of PRIMARY KEY constraint: Cannot insert duplicate key in object违反 PRIMARY KEY 约束:无法在对象中插入重复键
【发布时间】:2013-05-16 08:33:04
【问题描述】:

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

{"违反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 提要导入数据库。

  • 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 中有这些 AutoMapper:

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,那么它会崩溃

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

【问题讨论】:

  • 在相关说明中:如果你想有两个父母,那么为 Parent1 和 Parent2 添加外键(历史上的母亲和父亲)。为父母身份创建多对多关系允许某人拥有 20 多个父母,例如 Eric Cartman。
  • 再想一想:由于一个家长可能在学生中有多个孩子,如果您尝试两次插入同一个家长,您将违反主键约束。

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


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    如果两个或更多 student in _xStudents.Descendants("Student") 引用同一个父项(按 id),然后您创建两个或更多具有相同 id 的 ParentDTOs,因此您尝试在您的 @987654323 中插入​​相同的主键两次@类。

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

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

    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()
                };
    

    【讨论】:

    • 谢谢,这个解决方案帮助我继续。
    【解决方案3】:

    我收到了用户定义表类型的错误。在建立数据关系时,我有时会多次提取相同的记录。如果合适,在声明 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),
      ...
    )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-30
      • 1970-01-01
      • 1970-01-01
      • 2020-03-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多