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