简体   繁体   中英

Entity Framework 6 -> External Service -> self referencing foreign key -> The INSERT statement conflicted with the FOREIGN KEY constraint

We are writing an application for an external service that feeds us data like this:

{"businessSystemId":"SE","caseId":1,…,"relatedCases":[{"businessSystemId":"SE","caseId":1,"relationshipNo":123,"relatedBusinessSystemId":"SE","relatedCaseId":2,"…

{"businessSystemId":"SE","caseId":2,…,"relatedCases":[{"businessSystemId":"SE","caseId":2,"relationshipNo":123,"relatedBusinessSystemId":"SE","relatedCaseId":1,"…

Entity framework classes:

public class TPCase
{
    [Key, Column(Order = 0)]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public string BusinessSystemId { get; set; }

    [Key, Column(Order = 1)]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int CaseId { get; set; }

    public virtual ICollection<TPRelatedCase> RelatedCases { get; set; }
}

public class TPRelatedCase
{
    [Key, Column(Order = 0)]
    [ForeignKey("TPCase")]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public string BusinessSystemId { get; set; }

    [Key, Column(Order = 1)]
    [ForeignKey("TPCase")]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int CaseId { get; set; }

    [InverseProperty("RelatedCases")]
    public virtual TPCase TPCase { get; set; }

    [Key, Column(Order = 2)]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int RelationshipNo { get; set; }

    [ForeignKey("RelatedCase"), Column(Order = 3)]
    public string RelatedBusinessSystemId { get; set; }

    [ForeignKey("RelatedCase"), Column(Order = 4)]
    public int? RelatedCaseId { get; set; }

    public virtual TPCase RelatedCase { get; set; }
}

The data feed and classes are slimmed down to focus on the problem at hand. When I try to add any of these cases I get the following error:

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.TPRelatedCases_dbo.TPCases_RelatedBusinessSystemId_RelatedCaseId". The conflict occurred in database "Test.Database", table "dbo.TPCases".

The statement has been terminated.

This is of course understandable given our model but it means I cannot use the normal dbContext.Cases.Add(newCase); for any of the objects above. Normally I can loop through the RelatedCases collection recursively and add the foreign keys first but in this case it leads to an infinite loop . Do I need to empty the TPCase.RelatedCases collection and add relationships after the cases has been added or can entity framework solve this somehow?

I ended up using a HashSet with a custom IEqualityComparer to avoid any duplicates for TPRelatedCase . I had to change from ICollection<TPRelatedCase> RelatedCases to IList<TPRelatedCase> RelatedCases in TPCase to use the indexer (array square bracket operator).

IEqualityComparer:

public class TPRelatedCaseComparer : IEqualityComparer<TPRelatedCase>
{
    public bool Equals(TPRelatedCase x, TPRelatedCase y)
    {
        var xJson = JsonConvert.SerializeObject(x);
        var yJson = JsonConvert.SerializeObject(y);

        return xJson.Equals(yJson);
    }

    public int GetHashCode(TPRelatedCase obj)
    {
        return obj.BusinessSystemId.GetHashCode() + obj.CaseId.GetHashCode() + obj.RelationshipNo.GetHashCode();
    }
}

Method to get new information and add RelatedCases in a later stage ( AddOrUpdateRelatedCases ):

public void AddOrUpdateNewInformation(InformationToGet info)
{
    AddOrUpdatePossibleForeignKeys(info);

    var comparer = new TPRelatedCaseComparer();
    var relatedCases = new HashSet<TPRelatedCase>(comparer);

    _logger.Info($"Started updating cases");
    foreach (var infoCaseKey in info.CaseKeys)
    {
        var newCase = _integrationApiService.GetCase(infoCaseKey.BusinessSystemId, infoCaseKey.CaseId);

        AddOrUpdateCase(newCase, relatedCases);

        AddOrUpdateRelatedCases(relatedCases);
    }
}

Recursive method to add all cases but no RelatedCases.

private HashSet<TPRelatedCase> AddOrUpdateCase(TPCase newCase, HashSet<TPRelatedCase> relatedCases)
{
    if (newCase?.RelatedCases?.Count > 0)
    {
        relatedCases.UnionWith(newCase.RelatedCases);

        foreach (var relatedCase in newCase.RelatedCases)
        {
            if (relatedCase.RelatedCaseId.HasValue)
            {
                var newRelatedCase = _integrationApiService.GetCase(relatedCase.RelatedBusinessSystemId, relatedCase.RelatedCaseId.Value);

                for (int i = 0; i < newRelatedCase.RelatedCases.Count - 1; i++)
                {
                    if (relatedCases.Any(x =>
                        x.BusinessSystemId == newRelatedCase.RelatedCases[i].BusinessSystemId && x.CaseId ==
                        newRelatedCase.RelatedCases[i].CaseId && x.RelationshipNo == newRelatedCase.RelatedCases[i].RelationshipNo))
                    {
                        newRelatedCase.RelatedCases.Remove(newRelatedCase.RelatedCases[i]);
                    }
                }

                relatedCases = AddOrUpdateCase(newRelatedCase, relatedCases);
            }
        }
    }
    //Need to clear RelatedCases due to the fact that circular references could happen and therefore a recursive method is not possible. 
    //Would lead to FOREIGN KEY constraint exception.
    newCase.RelatedCases = null;

    var current = _dbContext.Cases.Find(newCase.BusinessSystemId, newCase.CaseId);
    if (current == null)
    {
        _dbContext.Cases.Add(newCase);
    }
    else
    {
        _dbContext.Entry(current).CurrentValues.SetValues(newCase);
    }

    _dbContext.SaveChanges();

    return relatedCases;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM