EF核心 - 自我引用多对多关系

时间:2017-04-12 15:35:11

标签: c# many-to-many entity-framework-core self-referencing-table

实体模型:

public class DocumentType : CodeBase
{
    [Required]
    [MaxLength(100)]
    public string Name { get; set; }

    public TimeSpan? Productiontime { get; set; }

    public bool IsDeliverable { get; set; }

    public virtual ICollection<DocumentTypeRetractRelation> DocumentTypes { get; set; }
    public virtual ICollection<DocumentTypeRetractRelation> RetractDocumentTypes { get; set; }
}

关系模型:

/// <summary>
/// Relationship between document types showing which documenttypes can 
/// retracted when delivering a new document.
/// </summary>
[Table("DocumentTypeRetractRelation")]
public class DocumentTypeRetractRelation
{
    public int DocumentTypeId { get; set; }
    public virtual DocumentType DocumentType { get; set; }

    public int RetractDocumentTypeId { get; set; }
    public virtual DocumentType RetractDocumentType { get; set; }
}

模型构建器:

modelBuilder.Entity<DocumentTypeRetractRelation>().HasKey(x => new { x.DocumentTypeId, x.RetractDocumentTypeId });

modelBuilder.Entity<DocumentTypeRetractRelation>()
    .HasOne(x => x.DocumentType)
    .WithMany(x => x.DocumentTypes)
    .HasForeignKey(x => x.DocumentTypeId);

modelBuilder.Entity<DocumentTypeRetractRelation>()
    .HasOne(x => x.RetractDocumentType)
    .WithMany(x => x.RetractDocumentTypes)
    .HasForeignKey(x => x.RetractDocumentTypeId);

更新作家:

    public async Task<DocumentType> UpdateAsync(DocumentTypeUpdateDto documentTypeUpdateDto)
    {
        using (IUnitOfWork uow = UowProvider.CreateUnitOfWork<EntityContext>())
        {
            var documentTypeRepo = uow.GetCustomRepository<IDocumentTypeRepository>();

            var existingDocument = await documentTypeRepo.GetAsync(documentTypeUpdateDto.Id);

            if (existingDocument == null)
                throw new EntityNotFoundException("DocumentType", existingDocument.Id);

            foreach (var retractDocumentTypeId in documentTypeUpdateDto.RetractDocumentTypeIds)
            {
                existingDocument.RetractDocumentTypes.Add(new DocumentTypeRetractRelation()
                {
                    DocumentTypeId = existingDocument.Id,
                    RetractDocumentTypeId = retractDocumentTypeId
                });
            }

            documentTypeRepo.Update(existingDocument);

            await uow.SaveChangesAsync();

            return existingDocument;
        }
    }

尝试更新existingDocument时,出现以下错误:

  

实体类型'DocumentTypeRetractRelation'的实例不能   跟踪,因为具有相同密钥的此类型的另一个实例是   已被跟踪。添加新实体时,对于大多数关键类型a   如果没有设置密钥,则将创建唯一的临时密钥值(即,如果   key属性被赋予其类型的默认值)。如果你   明确设置新实体的关键值,确保不这样做   与现有实体或为其他实体生成的临时值发生冲突   新实体。附加现有实体时,请确保只有一个实体   具有给定键值的实体实例附加到上下文。

1 个答案:

答案 0 :(得分:2)

问题不在于自引用,而是应用多对多的集合修改,这些修改生成具有与异常消息中所述相同的PK的不同DocumentTypeRetractRelation个对象。

EF Core中当前的正确方法是确保加载RetractDocumentTypes existingDocument(包含原始值),然后使用现有合并或创建新{{1}合并更改对象。

替换以下代码

DocumentTypeRetractRelation

foreach (var retractDocumentTypeId in documentTypeUpdateDto.RetractDocumentTypeIds)
{
    existingDocument.RetractDocumentTypes.Add(new DocumentTypeRetractRelation()
    {
        DocumentTypeId = existingDocument.Id,
        RetractDocumentTypeId = retractDocumentTypeId
    });
}

这将处理添加,删除和未更改的关系。您可以执行与// existingDocument.RetractDocumentTypes should be loaded (either eager or explicit) existingDocument.RetractDocumentTypes = ( from retractDocumentTypeId in documentTypeUpdateDto.RetractDocumentTypeIds join existingRelation in existingDocument.RetractDocumentTypes on retractDocumentTypeId equals existingRelation.RetractDocumentTypeId into existingRelations select existingRelations.FirstOrDefault() ?? new DocumentTypeRetractRelation() { DocumentTypeId = existingDocument.Id, RetractDocumentTypeId = retractDocumentTypeId }).ToList(); 类似的操作。

实际上看一下你的模型,上面的代码应该是DocumentTypes集合(因为你收到了DocumentTypes,它与文档RetractDocumentTypeIds结合形成Id收集内容)。因此,只需将DocumentTypes替换为RetractDocumentTypes