使用外键发布新实体会导致创建另一个外部实体而不是引用现有实体

Posted

技术标签:

【中文标题】使用外键发布新实体会导致创建另一个外部实体而不是引用现有实体【英文标题】:Posting a new entity with foreign key causes creating another foreign entity instead of referencing existing one 【发布时间】:2021-11-26 17:42:56 【问题描述】:

我有一个多层洋葱架构的 asp.net 应用程序,目前在表中发布新实体时遇到问题,该表与另一个表具有一对多的外键关系。

这是我的父实体:

    public class Feature : Entity

    public string Name  get; set; 
    public decimal Price  get; set; 
    public FeatureType FeatureType  get; set; 
    public ICollection<ConfigurationFeatureState> Configurations  get; set; 

这是一个参考:

public class FeatureType: Entity

    public string Name  get; set; 
    public bool IsOptional  get; set; 
    public ICollection<Feature> Features  get; set; 

实体模型只是为它们添加了一个 ID

public abstract class Entity 

    public Guid ID  get; private set;  = Guid.NewGuid();

所以,一个 Feature 可能只有一个 FeatureType,但 FeatureType 有很多 Features。

我正在使用 FeatureDto 进行演示,它被映射到 FeatureService 中的 Feature。

这是一个 FeatureDto,它只显示一个 GUID FeatureTypeID insted of FeatureType 实体:

public class FeatureDto : BaseDto

    public string Name  get; set; 
    public decimal Price  get; set; 
    public Guid FeatureTypeID  get; set; 
    

这是我的 FeatureController POST 方法:

[HttpPost]
public async Task<IActionResult> Post([FromBody] FeatureDto newFeatureDto)

    var featureDto = await _featureService.CreateAsync(newFeatureDto);
    return CreatedAtRoute("FeatureById", new  id = featureDto.ID , featureDto);

这里有一个来自 FeatureService 的 CreateAsync 方法,它是从一个泛型基类调用的(TDto = FeatureDto,TEntity = Feature):

public async Task<TDto> CreateAsync(TDto newEntityDto)

    var entity = Mapper.Map<TEntity>(newEntityDto);
    _repository.Create(entity);
    await UnitOfWork.SaveAsync();

    var dto = Mapper.Map<TDto>(entity);
    return dto;

我正在使用AutoMapper 将 Feature 映射到 FeatureDto,反之亦然,使用以下映射:

CreateMap<Feature, FeatureDto>()
    .ForMember(dto => dto.FeatureTypeID, opt => opt.MapFrom(db => db.FeatureType.ID))
    .ReverseMap();

这就是问题所在:每当我尝试将一个新功能发布到数据库中并引用已经存在的 FeatureType 时,就会创建另一个空的 FeatureType(我不需要)。

示例如下:

我正在通过 Postman 使用此请求正文发布新功能:


    "name": "LED Strip Neon Car Bottom",
    "price": 200.00,
    "featureTypeID": "41b737f9-1649-4a66-94c7-065d099408e6" // value with this ID is already present in FeatureType table

并收到错误Microsoft.Data.SqlClient.SqlException (0x80131904):无法将值 NULL 插入到列“名称”、表“DB.dbo.FeatureTypes”中;列不允许空值。 插入 失败。声明已终止。

这是因为在 CreateAsync 方法期间,一个新的 FeatureType 与一个 Feature 一起被创建: CreateAsync method debugging.

为什么会发生这种情况,我如何确保不会插入新的 FeatureType,而是从数据库中选择已经存在的 FeatureType?

我已经使用 Fluent API 配置了所有关系,并且它们已在数据库中正确设置,因此外键存在:feature table in db

如果需要,这里也是配置类:

class FeatureEntityConfiguration : IEntityTypeConfiguration<Feature>

    public void Configure(EntityTypeBuilder<Feature> builder)
    
        builder.HasOne(f => f.FeatureType).WithMany(ft => ft.Features).IsRequired().HasForeignKey("FeatureTypeID");

        builder.HasKey(f => f.ID);

        builder.Property(f => f.Name).IsRequired().HasMaxLength(100);

        builder.Property(f => f.Price).HasColumnType("decimal(18,2)");
    

对不起,如果这是一个奇怪的问题,但我是这个东西的新手。提前谢谢!

【问题讨论】:

【参考方案1】:

我不确定你的所有层都在哪里,但你只需要为 FeatureTypeId 设置导航属性(可能是 EF Core 中的 Shadow Property),或者在 (或)

_repository.Create(entity);

【讨论】:

【参考方案2】:

您是否尝试过获取要素类型并手动分配导航属性?

public async Task<TDto> CreateAsync(TDto newEntityDto)

    var entity = Mapper.Map<TEntity>(newEntityDto);

    /***/
    var featureType = _featureTypeRepository.Get(newEntityDto.featureTypeID);
    entity.FeatureType = featureType;
    /***/ 

    _repository.Create(entity);
    await UnitOfWork.SaveAsync();

    var dto = Mapper.Map<TDto>(entity);
    return dto;

【讨论】:

哦,是的,确实如此。我手动忽略了 FeatureType 映射,只是在我的 FeatureService (而不是基本通用服务)中覆盖了 CreateAsync 方法,并且效果很好。谢谢!

以上是关于使用外键发布新实体会导致创建另一个外部实体而不是引用现有实体的主要内容,如果未能解决你的问题,请参考以下文章

在实体框架中找出导致异常的确切实体

C# 实体框架向来自外部库的实体添加新列

实体框架 4.1 代码优先外键 ID

鹡鸰页面模型:外键实体+多参数

数据库建模:按实体类型的空外键

015_数据建模时,JavaBean的实体类中怎样处理外键