【问题标题】:Entity Framework inserts existing navigation property entity实体框架插入现有的导航属性实体
【发布时间】:2017-08-10 08:20:00
【问题描述】:

我正在使用 Entity Framework 6,但我不断收到以下错误:

违反主键约束“PK_AssignmentType”。无法在对象“dbo.AssignmentType”中插入重复键。

如您所见,我尝试将实体 AssignmentType 的状态更改为 unchanged。当我为实体Assignment 分配AssignmentType 的外键,然后将导航属性AssignmentType 设置为null 时,我仍然得到相同的错误(在Assignments 的foreach 循环中,而不是在Comments foreach循环)。

Entity Framework 在哪里跟踪实体AssignmentType,为什么它仍然认为它是一个新实体而不是现有实体?

实体ReportingAssignment之间存在多对多关系。

    [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; }
}

Assignment.cs

[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; }        
}

【问题讨论】:

  • 你有外键吗:Assignment.AssignmentTypeId?
  • 是的,我愿意。在 ReportingDTO 中,此字段等于 null。
  • 你能发布你的数据模型吗?
  • 我更新了我的问题。

标签: c# sql sql-server entity-framework


【解决方案1】:

试试这样的:

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。每次请求命中时都会构造您的 Controller,因此每次也会构造您的 DbContext。当您创建新的 DbContext 时,它不知道以前的操作,例如返回 Assignment 或其他任何内容(如果您在查询期间指定 AsNoTracking() 也是这种情况)。换句话说,如果实体没有被 DbContext 跟踪,那么 EF 会尝试添加它。
为了防止这种情况,您可以(并且确实应该)使用外键而不是导航属性。或者你可以Attach()每个已知的实体(但如果你有外键就没用了)。对于多对多关系,您必须在添加/更新新实体之前查询数据库,否则,您将有达布隆。

或者,您也可以使用我所说的“链接表”。这只是一个介于两个主要课程之间的课程。 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;}
}

通过这种方式,您可以使用_db.ABs.Add(new AB{AId = 1, BId = 2}) 添加一个新的 AB(或删除一个),而无需处理其余部分。

【讨论】:

  • 如果我只添加 if 语句,然后将其添加到报告中(就像您所做的那样),我仍然会遇到上述相同的错误。另外我应该提到,可能会有新的任务或更新的任务。
  • 您的意思是要在单个方法中添加或更新分配?加上添加报告和 cmets?
  • 是的,正确。用户通过一个向导,最后他保存了包含所有这些数据的报告。
  • 我已经更新了我的答案。现在该方法处理新的和更新的分配。顺便说一句,你真的应该避免这种逻辑。通常建议使用“原子”操作(可能使用存储库),因为它更易于维护和调试。
  • 拥有一个“做所有事情”的方法通常是不好的,并且违反了 OOP 的“关注点分离”原则。简而言之,在这里,您的方法Add 接收请求,处理数据,将其保存到数据库并发送响应。理想情况下,它应该只调用其他方法来完成没有收到请求并发送响应的所有其他事情。我不会更进一步,但你可以在这里看看:chsakell.com/2015/02/15/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-21
  • 1970-01-01
  • 1970-01-01
  • 2016-03-29
  • 2016-05-29
相关资源
最近更新 更多