複雑なモデルを永続化したい場合、このエラーが発生します。どこから来たのかはわかっていると思いますが、解決方法がわかりません。いくつかのフィードをインポートし、子 (多対多) を含むオブジェクトを自動的に作成します。
{"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 フィードをデータベースにインポートする必要があります。
- 学生.xml
- 親.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)
モデル
そこで、DTO を含む 3 つのモデルを作成しました。
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()
. 親モデルの重複する ForeignKey について文句を言います。だから私は考えています:
これは多対多の関係であるため、John Doe の妹である Jane Doe が同じ Dad Doe を挿入しようとすると、クラッシュします。
では、マップされたビジネス オブジェクトのセット全体が各エンティティへの参照を 1 つだけ持つようにするにはどうすればよいでしょうか。重複したパパとママを削除するには?私はおそらく、件名についてもこれを行いたいと考えています。