Home > database >  Posting a new entity with foreign key causes creating another foreign entity instead of referencing
Posting a new entity with foreign key causes creating another foreign entity instead of referencing

Time:10-07

I have a multi-layered onion architectured asp.net application and currently fasing an issue with POSTing a new entity in a table, which has one-to-many foreign key relation with another table.

Here is my parent entity:

    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; }
}

and here is a referenced one:

public class FeatureType: Entity
{
    public string Name { get; set; }
    public bool IsOptional { get; set; }
    public ICollection<Feature> Features { get; set; }
}

Entity model just adds an ID to them

public abstract class Entity 
{
    public Guid ID { get; private set; } = Guid.NewGuid();
}

So, a Feature may have a single FeatureType only, but FeatureType has many Features.

I'm using FeatureDto for a presentation, which is being mapped to Feature in FeatureService.

Here is a FeatureDto, which just shows a GUID FeatureTypeID insted of FeatureType entity:

public class FeatureDto : BaseDto
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public Guid FeatureTypeID { get; set; }
    
}

Here is my FeatureController POST method:

[HttpPost]
public async Task<IActionResult> Post([FromBody] FeatureDto newFeatureDto)
{
    var featureDto = await _featureService.CreateAsync(newFeatureDto);
    return CreatedAtRoute("FeatureById", new { id = featureDto.ID }, featureDto);
}

And here is a CreateAsync method from FeatureService, which is called from a generic base class (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;
}

I'm using AutoMapper to map Feature to FeatureDto and vice versa using the following map:

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

And here is the problem: whenever I try to POST a new Feature into the db referring to already existing FeatureType, another empty FeatureType is being created (which I don't need).

Here is the example:

I'm posting a new Feature via Postman with this request body:

{
    "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
}

and getting the error Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert the value NULL into column 'Name', table 'DB.dbo.FeatureTypes'; column does not allow nulls. INSERT fails. The statement has been terminated.

This is because during the CreateAsync method a new FeatureType is being created along with a Feature: CreateAsync method debugging .

Why is this happening and how can I assure that instead of inserting a new FeatureType, the already existing one will be selected from the db?

I have configured all the relations using Fluent API and they were set up properly in the DB, so that the Foreign Key is present there: feature table in db

Here is the configuration class as well, if you need it:

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)");
    }
}

Sorry if that's a weird question, but I'm quite a newbie with this stuff. Thank you in advance!

CodePudding user response:

I'm not sure in all your layers where this goes, but you just need to set the Navigation Property (possibly a Shadow Property in EF Core), for the FeatureTypeId, or explicitly mark the FeatureType as Unchanged, after (or in)

_repository.Create(entity);

CodePudding user response:

Have you tried to get the feature type and manually assign your navigation property?

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;
}
  • Related