EFCore: Entities association error during a simple property update

Perrier picture Perrier · Jul 26, 2018 · Viewed 7.9k times · Source

I'd like to set one bool property in my controller and save it to the database. EF throws an error about other properties that are not even modified.

The association between entities 'User' and 'RequestDetail' with the key value 'System.InvalidOperationException: The association between entities 'User' and 'RequestDetail' with the key value '{Id: 40}' has been severed but the relationship is either marked as 'Required' or is implicitly required because the foreign key is not nullable. If the dependent/child entity should be deleted when a required relationship is severed, then setup the relationship to use cascade deletes.

If I call my method with an additional parameter it has to change one RequestDetail record's RequestSent property and that is working great. But calling the method without this additional parameter it has to change this property on more than one RequestDetail records. And this is where it throws the error. I don't modify anything associated with User. If it has to do more records at once it throws this error. Even if I rewrite the foreach into a while with FirstOrDefaults() and immediate SaveChanges() it throws error on the second round.

My method:

var head = await _ctx.RequestHeads.FirstOrDefaultAsync(x=>x.Id == d.Id);
if (!d.DetailId.HasValue) {
            var details = _ctx.RequestDetails.Include(x=>x.BuyerUser)
                            .Where(x=>x.RequestHeadId == head.Id); 
//not working, always throws an error if it has to modify more than one record
            await details.ForEachAsync(detail => {
                detail.RequestSent = true;
            });
        } else {
            var detail = head.Details.FirstOrDefault(x=>x.Id == d.DetailId.Value); //works ok, always
            detail.RequestSent = true;
        }
        await _ctx.SaveChangesAsync();

My models:

public class RequestHead
{
    [Key, MaxLength(15)]
    public string Id { get; set; }
    public DateTime CreateDate { get; set; }
    public int CreateUserId { get; set; }
    [ForeignKey("CreateUserId")]
    public User CreateUser { get; set; }

    public DateTime? AcceptDate { get; set; }
    public int? AcceptUserId { get; set; }
    [ForeignKey("AcceptUserId")]
    public User AcceptUser { get; set; }

    public DateTime? CloseDate { get; set; }
    public int? CloseUserId { get; set; }
    [ForeignKey("CloseUserId")]
    public User CloseUser { get; set; }     
    public int? CloseReason { get; set; }   
    public bool IsArchive { get; set; }

    [MaxLength(8)]
    public string OrganizationCode { get; set; }

    [ForeignKey("OrganizationCode")]
    public Organization Organization { get; set; }

    public virtual ICollection<RequestDetail> Details { get; set; }
    public virtual ICollection<RequestAttachment> Attachments { get; set; }
}

public class RequestDetail
{
    [Key]
    public int Id { get; set; }

    public string RequestHeadId { get; set; }

    [ForeignKey("RequestHeadId")]
    public RequestHead RequestHead { get; set; }

    [MaxLength(20)]
    public string ProductCode { get; set; }

    [ForeignKey("ProductCode")]
    public Product Product { get; set; }
    public string ProductName { get; set; }
    public bool NoProductCode { get; set; }
    public string Description { get; set; }
    public DateTime CreateDate { get; set; }
    public int CreateUserId { get; set; }
    [ForeignKey("CreateUserId")]
    public User CreateUser { get; set; }


    public DateTime? DelegateDate { get; set; }
    public int? DelegateUserId { get; set; }
    [ForeignKey("DelegateUserId")]
    public User DelegateUser { get; set; }


    public int? BuyerUserId { get; set; }
    [ForeignKey("BuyerUserId")]
    public User BuyerUser { get; set; }

    public bool RequestSent { get; set; }
    public virtual ICollection<RequestAttachment> Attachments { get; set; }

}

Context:

modelBuilder.Entity<RequestHead>()
            .HasOne(r=>r.CreateUser)
            .WithOne().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<RequestHead>()
            .HasMany(r => r.Details)
            .WithOne(x=>x.RequestHead)
            .HasForeignKey(rd => rd.RequestHeadId);
modelBuilder.Entity<RequestDetail>()
            .HasOne(r=>r.CreateUser)
            .WithOne().OnDelete(DeleteBehavior.Restrict);

Answer

Perrier picture Perrier · Jul 26, 2018

The solution was to change RequestDetail<=>User relations from WithOne() to WithMany(). Although the error message is somewhat misleading, a possible explanation from @IvanStoev is the following:

I guess with one-to-one the code expects single record, so when the second record with the same FK comes in, they get confused and decided that the record is deleted