C# 实体框架插入现有的导航属性实体
我使用的是Entity Framework 6,不断出现以下错误: 违反主键约束“PK_AssignmentType”。无法在对象“dbo.AssignmentType”中插入重复键 如您所见,我试图将实体的状态更改为C# 实体框架插入现有的导航属性实体,c#,sql,sql-server,entity-framework,C#,Sql,Sql Server,Entity Framework,我使用的是Entity Framework 6,不断出现以下错误: 违反主键约束“PK_AssignmentType”。无法在对象“dbo.AssignmentType”中插入重复键 如您所见,我试图将实体的状态更改为AssignmentType到unchanged。当我为实体Assignment分配AssignmentType的外键,然后将导航属性AssignmentType设置为null时,我仍然会得到相同的错误(在Assignments的foreach循环中,而不是在Commentsfor
AssignmentType
到unchanged
。当我为实体Assignment
分配AssignmentType
的外键,然后将导航属性AssignmentType
设置为null时,我仍然会得到相同的错误(在Assignments
的foreach循环中,而不是在Comments
foreach循环中)
实体框架在哪里跟踪实体AssignmentType
,为什么它仍然认为它是一个新实体而不是现有实体
实体报告
和分配
之间存在多对多关系
[Route("")]
[HttpPost]
public IHttpActionResult Add(ReportingDTO data)
{
Reporting reporting = new Reporting { ID = Guid.NewGuid(), Date = DateTime.Now };
foreach (Assignment assignment in data.Assignments)
{
_db.Entry(assignment.AssignmentType).State = EntityState.Unchanged;
if (assignment.Reporting.Count > 0)
{
_db.Entry(assignment).State = EntityState.Added;
_db.Entry(assignment).State = EntityState.Modified;
}
reporting.Assignment.Add(assignment);
}
foreach (Comment comment in data.Comments)
{
comment.ID = Guid.NewGuid();
comment.AssignmentID = comment.Assignment.ID;
comment.Assignment = null;
comment.ReportingID= reporting.ID;
}
using(var transaction = _db.Database.BeginTransaction())
{
_db.Reporting.Add(reporting);
_db.Comments.AddRange(data.Comments);
_db.SaveChanges();
transaction.Commit();
}
return Ok(reporting);
}
Reporting.cs
[Table("Reporting")]
public partial class Reporting
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Reporting()
{
Assignment= new HashSet<Assignment>();
}
[Key]
public Guid ID{ get; set; }
[Column(TypeName = "date")]
public DateTime Date{ get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Assignment> Assignment{ get; set; }
}
[Table("Assignment")]
public partial class Assignment
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Assignment()
{
Reporting = new HashSet<Reporting>();
}
[Key]
public Guid OID { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
public Guid AssignmentTypeID { get; set; }
[Required]
[StringLength(100)]
public string Project { get; set; }
public bool Completed { get; set; }
[ForeignKey("AssignmentTypeID")]
public virtual AssignmentType AssignmentType { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Reporting> Reporting { get; set; }
}
AssignmentType
[Table("AssignmentType")]
public partial class AssignmentType
{
[Key]
public Guid ID { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
}
试着这样做:
public IHttpActionResult Add(ReportingDTO data)
{
Reporting reporting = new Reporting { ID = Guid.NewGuid(), Date = DateTime.Now };
foreach (Assignment assignment in data.Assignments)
{
if (assignment.ID != null)
{
_db.Assignments.Attach(assignment);
_db.Entry(assignment).State = EntityState.Modified;
}
else
{
assignment.ID = Guid.NewGuid();
}
if (assignment.AssignmentType != null)
{
assignment.AssignmentTypeID = assignment.AssignmentType.ID;
assignment.AssignmentType = null;
}
}
reporting.Assignment.AddRange(data.Assignments);
foreach (Comment comment in data.Comments)
{
comment.ID = Guid.NewGuid();
comment.AssignmentID = comment.Assignment.ID;
comment.Assignment = null;
comment.ReportingID = reporting.ID;
}
_db.Reporting.Add(reporting);
_db.Comments.AddRange(data.Comments);
_db.SaveChanges();
return Ok(reporting);
}
所有Entry()
操作在您的上下文中都是无用的,除非您正在更新实体。在这种情况下,您必须在更新其状态之前Attach()
更新的实体
另外,为了回答您最初的问题:EF添加了一个新的AssignmentType,因为它丢失了现有AssignmentType的轨迹。每次请求命中时都会构造控制器,因此每次都会构造DbContext。创建新的DbContext时,它不知道以前的操作,例如返回赋值
或其他任何操作(如果在查询过程中指定AsNoTracking()
,也会出现这种情况)。换句话说,如果DbContext没有跟踪该实体,则EF将尝试添加该实体。为了防止出现这种情况,您可以(而且确实应该)使用外键而不是导航属性。或者您可以
Attach()
每个已知的实体(但是如果您有外键,则它是无用的)。对于许多关系,您必须在添加/更新新实体之前查询数据库,否则将出现doublons
或者,您也可以使用我所说的“链接表”。它只是一个介于两个主要类之间的类。EF已经为您的所有多人关系(检查您的数据库)完成了这项工作。基本上是这样的:
public class A
{
public int Id {get; set;}
public virtual List<AB> ABs { get; set; }
}
public class B
{
public int Id {get; set;}
public virtual List<AB> BAs {get; set; }
}
public class AB
{
public int AId {get;set;}
public virtual A {get; set;}
public int BId {get;set;}
public virtual B {get; set;}
}
公共A类
{
公共int Id{get;set;}
公共虚拟列表ABs{get;set;}
}
公共B级
{
公共int Id{get;set;}
公共虚拟列表BAs{get;set;}
}
公共AB类
{
公共int-AId{get;set;}
公共虚拟A{get;set;}
公共整数BId{get;set;}
公共虚拟B{get;set;}
}
通过这种方式,您可以使用
\u db.ABs.add(new AB{AId=1,BId=2})
添加一个新的AB(或删除一个),而不必考虑其余部分。您是否有外键,如:Assignment.AssignmentTypeId?是的。在ReportingDTO中,此字段等于null。您能发布您的数据模型吗?我更新了我的问题。如果我只添加If语句,然后将其添加到报告中(就像您所做的那样),我仍然会得到与前面提到的相同的错误。我还应该提到,可以有新的分配或更新的分配。你的意思是你想在一个方法中添加或更新分配?加上添加报告和评论?是的,正确。用户通过一个向导,最后他保存了包含所有这些数据的报告。我已经更新了我的答案。现在,该方法处理新的和更新的分配。顺便说一下,你真的应该避免这种逻辑。通常建议使用“原子”操作(可能使用存储库),因为它更易于维护和调试。使用“做所有事情”的方法通常是不好的,并且违反了“关注点分离”的原则,即OOP。简而言之,这里,您的方法Add
接收请求,处理数据,将其保存到数据库并发送响应。理想情况下,它应该只调用其他方法来执行其他没有接收请求和发送响应的操作。我不想再多说了,但你可以看看这里: