EF Core - 自引用多对多关系
EF Core - Self-referencing Many to Many relationship
实体模型:
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 时出现以下错误:
The instance of entity type 'DocumentTypeRetractRelation' cannot be
tracked because another instance of this type with the same key is
already being tracked. When adding new entities, for most key types a
unique temporary key value will be created if no key is set (i.e. if
the key property is assigned the default value for its type). If you
are explicitly setting key values for new entities, ensure they do not
collide with existing entities or temporary values generated for other
new entities. When attaching existing entities, ensure that only one
entity instance with a given key value is attached to the context.
问题不在于自引用,而是应用 many-to-many collection 修改生成不同的 DocumentTypeRetractRelation
objects 与异常消息中所述的相同 PK .
当前 EF Core 中的正确方法是确保加载 existingDocument
中的 RetractDocumentTypes
(包含原始值),然后通过使用现有的或创建新的 DocumentTypeRetractRelation
objects.
替换以下代码
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
collection(因为你收到了 RetractDocumentTypeIds
,它与文档 Id
形成 DocumentTypes
collection 内容)。所以只需将 RetractDocumentTypes
替换为 DocumentTypes
.
实体模型:
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 时出现以下错误:
The instance of entity type 'DocumentTypeRetractRelation' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
问题不在于自引用,而是应用 many-to-many collection 修改生成不同的 DocumentTypeRetractRelation
objects 与异常消息中所述的相同 PK .
当前 EF Core 中的正确方法是确保加载 existingDocument
中的 RetractDocumentTypes
(包含原始值),然后通过使用现有的或创建新的 DocumentTypeRetractRelation
objects.
替换以下代码
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
collection(因为你收到了 RetractDocumentTypeIds
,它与文档 Id
形成 DocumentTypes
collection 内容)。所以只需将 RetractDocumentTypes
替换为 DocumentTypes
.