I am trying to update a database row by reading the entity (using Entity Framework 6), mapping new properties onto that entity (using AutoMapper) and then calling context.SaveChanges()
.
The problem I am having surrounds the navigational properties within my entity. It appears that during the mapping, a new object is being created and assigned to the navigational property, rather than the existing one's properties simply being updated.
Here's are my domain objects:
public class ParagraphComponent : IReportComponent
{
public Guid ComponentId { get; set; }
public float LineHeight { get; set; }
public ReportTextList TextItems { get; set; } = new ReportTextList();
}
public class ReportTextList : IList<ReportText>
{
private readonly IList<ReportText> _list = new List<ReportText>();
public int Count => _list.Count;
public bool IsReadOnly => _list.IsReadOnly;
// Overrides for IList...
public void Add(ReportText item)
{
_list.Add(item);
}
// ...Remove(), Insert() etc.
}
public class ReportText
{
public Guid Id { get; set; }
public string Content { get; set; } = "";
}
Entity Framework entities:
public partial class ParagraphComponentEntity
{
public System.Guid ComponentId { get; set; } // ComponentId (Primary key)
public double LineHeight { get; set; } // LineHeight
public virtual System.Collections.Generic.ICollection<ReportTextEntity> ReportTexts { get; set; }
}
public partial class ReportTextEntity
{
public System.Guid Id { get; set; } // Id (Primary key)
public string Content { get; set; } // Content
}
What I am doing: I am taking data for a ParagraphComponent
from an API endpoint to perform an update. I load the existing component based on ParagraphComponent.Id
and then I map the new properties onto the existing entity.
This works fine:
public async Task<bool> EditComponent(IReportComponent editedComponent)
{
var currentParagraphComponentEntity = await Context
.ParagraphComponents
.FirstOrDefaultAsync(x => x.ComponentId == editedComponent.ComponentId)
.ConfigureAwait(false);
Mapper.Map(editedComponent as ParagraphComponent, currentParagraphComponentEntity);
Context.SaveChanges();
}
I can see in debug that the properties are mapped correctly, but when SaveChanges()
is called I get the following error:
Violation of PRIMARY KEY constraint 'PK_ReportText'. Cannot insert duplicate key in object 'dbo.ReportText'
It appears that the mapping process is assigning a new object to the ParagraphComponentEntity.ReportTexts
property, thus Entity Framework sees it as an "Add" rather than an "Update", so it tries adding a new row to that table which errors because of the primary key enforcing the Id to be unique.
My AutoMapper configuration:
CreateMap<ParagraphComponent, ParagraphComponentEntity>()
.ForMember(dest => dest.LineHeight, src => src.MapFrom(s => s.LineHeight))
.ForMember(dest => dest.ReportTexts, src => src.MapFrom(s => s.TextItems))
.ForMember(dest => dest.ComponentId, src => src.MapFrom(s => s.ComponentId))
.ForAllOtherMembers(src => src.Ignore());
If AutoMapper is creating a new instance for the ReportTexts
navigational property is the issue, how do I get around it?
CodePudding user response:
It seems that your code which is fetching data from database is only fetching the main resource ParagraphComponent
. Collection of ReportTexts
by default will not be fetched into EF context because as far as I know EF is lazy loading - you need to eagerly load referenced entities with .Include(..)
for instance.
If I am right - then before mapping your data into entity ReportTexts
collection is empty and your Mapping code is really creating new items in that collection. These entities are comming from outside of EF context (read about EF ChangeTracking) so 'it' thinks that these are new entities which needs to be inserted into database. These objects obviously already contains Id
set with existing keys - so that's where you get conflicts.
I think if you will eager load your entity, then EF should perform Update instead