当我想要持久化复杂模型时,我会收到此错误。我想我知道它来自哪里,但我不知道如何解决它。我正在导入一些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中,我遇到了一个设计缺陷:固定数量(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个引用每个实体;如何删除重复的爸爸和妈妈的?我可能也想为主题这样做。
答案 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将其视为新记录,并尝试插入。 我看到三个选项:
答案 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),
...
)