Search code examples
c#asp.net-coreentity-framework-core

Error saving EF Core values with repository pattern


I have the following classes:

public class Equipment
{
     public const string TableName = "ew_equipamento";

     public int EquipmentId { get; set; }
     public int IdLote { get; private set; }
     public LTE_dp_to_dp _Lote { get; set; }
}

//Mapp:
public void Configure(EntityTypeBuilder<Equipment> builder)
{
    builder.ToTable(Equipamento.TableName);
    builder.HasKey(x => x.EquipmentId);

    builder.HasOne(x => x._Lote)
             .WithOne()
             .HasForeignKey<Equipment>(x => x.LotId);

    builder.HasOne(x => x._Model)
             .WithOne()
             .HasForeignKey<Equipment>(x=> x.IdEquipmentModel);
}

// -------

public class LTE_dp_to_dp
{
     public const string TableName = "ew_lte_cd_to_ft";

     public int IdLote { get; set; }
    
     public List<LTE_dp_to_dp_Equipamento> _LoteEquipamentos { get; set; }
}

//Mapp:
public void Configure(EntityTypeBuilder<LTE_dp_to_dp> builder)
{
    builder.ToTable(LTE_dp_to_dp.TableName);
    builder.HasKey(x => x.IdLote);

    builder.HasMany(x => x._LoteEquipamentos)
             .WithOne(x=> x._Lote)
             .HasForeignKey(x => x.IdLote)
             .OnDelete(DeleteBehavior.Restrict);
}

// -----
public class LTE_dp_to_dp_Equipamento
{
     public const string TableName = "ew_lte_cd_to_ft_equipamento";

     public int EquipmentLoteId { get; set; }
     public int EquipmentId { get; set; }
     public Equipment _Equipment { get; set; }

     public int IdLote { get; set; }
     public LTE_dp_to_dp _Lote { get; set; }
}

//Mapp:
public void Configure(EntityTypeBuilder<LTE_dp_to_dp_Equipamento> builder)
{
    builder.ToTable(LTE_dp_to_dp_Equipamento.TableName);
    builder.HasKey(x => x.IdLoteEquipamento);

    builder.HasOne(x => x._Equipment)
             .WithOne()
             .HasForeignKey<LTE_dp_to_dp_Equipamento>(x => x.IdEquipamento);

    builder.HasOne(x => x._Lote)
             .WithMany(x=> x._LoteEquipamentos)
             .HasForeignKey(x => x.IdLote);
}

And I use the repositories pattern to search:

public async Task<LTE_dp_to_dp> GetAsync(int idLote)
         => await Query(x => x.IdLote == idLote)
             .Include(x => x._LoteEquipamentos.OrderBy(o => o.Caixa).ThenBy(o => o.IdLoteEquipamento))
             .ThenInclude(x => x._Equipment).ThenInclude(x => x._Model)
             .Include(x => x._LoteEquipamentos)
             .FirstOrDefaultAsync();

to update:

public void Update(TEntity model)
{
    DbSet.Attach(model);
    var entry = _context.Entry(model);
    entry.State = EntityState.Modified;
}

to save changes:

public async Task<bool> CommitAsync()
{
    int _changesNumber = await _context.SaveChangesAsync();
    return _changesNumber > 0;
}

I search using GetAsync, cycle through the items, and change the equipment.

LTE_dp_to_dp lot = await _lteDpToDpRepository.GetAsync(idLote);`

Then, I go through the items:

foreach(var i in lot._LoteEquipamentos)
{
    i._Equipment.LotId = other....
    _equipamentoRepository.Update(i._Equipamento);
}

await _uow.CommitAsync();

but I get the following error:

Unable to save changes because a circular dependency was detected in the data to be saved: 'Equipamento { 'IdEquipamento': 1571455 } [Modified] <-
Index { 'ModelEquipmentId': 25 } Equipment { 'EquipmentId': 1571456 } [Modified] <-
Index { 'ModelEquipmentId': 25 } Equipment { 'EquipmentId': 1571455 } [Modified]'.

I have already tried the following scenario:

foreach(var i in lot._LoteEquipamentos)
{
    i._Equipment.LotId = other....
    i._Equipment._Model = null;
    _equipamentoRepository.Update(i._Equipamento);
}

but I keep getting the same error...

EF Core 7..8

I expected update value from entity Equipamento, but I can't because of this error...


Solution

  • The issue here is with the one-to-one relationship you define between the Equipment and the LTE_dp_to_dp table.

    A one-to-one relationship is made up from:

    • One or more primary or alternate key properties on the principal entity.
    • One or more foreign key properties on the dependent entity.

    Simply put, you need to relate the identical columns. Please see Which way should a Foreign Key go in a one to one relationship? for more information.

    That said, to resolve the issue, remove the property public int LoteId { get; set; } from Equipment class and relate their primary keys as follows:

    Equipment model:

    public class Equipment
    {
        public const string TableName = "ew_equipamento";
        public int EquipmentId { get; set; }
        //public int LoteId { get; set; } //TODO Remove
        public LTE_dp_to_dp _Lote { get; set; }
    }
    

    LTE_dp_to_dp model:

    public class LTE_dp_to_dp
    {
        public const string TableName = "ew_lte_cd_to_ft";
        public int LoteId { get; set; }
        public Equipment _Equipment { get; set; } //TODO Add
        public List<LTE_dp_to_dp_Equipamento> _LoteEquipamentos { get; set; }
    }
    

    LTE_dp_to_dp_Equipamento model:

    public class LTE_dp_to_dp_Equipamento
    {
        public const string TableName = "ew_lte_cd_to_ft_equipamento";
        public int EquipmentLoteId { get; set; }
        //public int EquipmentId { get; set; } //TODO Remove
        //public Equipment _Equipment { get; set; } //TODO Remove
        public int LoteId { get; set; }
        public LTE_dp_to_dp _Lote { get; set; }
    }
    

    Configuration Mapping

    Equipment:

    public void Configure(EntityTypeBuilder<Equipment> builder)
    {
        builder.ToTable(Equipment.TableName);
        builder.HasKey(x => x.EquipmentId);
        // Define a 1:1 relationship.
        // You can define here or on the LTE_dp_to_dp config, but NOT on both!
        builder.HasOne(x => x._Lote)
            .WithOne(x => x._Equipment)
            .HasForeignKey<LTE_dp_to_dp>(x => x.LoteId);
    }
    

    LTE_dp_to_dp:

    public void Configure(EntityTypeBuilder<LTE_dp_to_dp> builder)
    {
        builder.ToTable(LTE_dp_to_dp.TableName);
        builder.HasKey(x => x.LoteId);
    }
    

    LTE_dp_to_dp_Equipamento :

    public void Configure(EntityTypeBuilder<LTE_dp_to_dp_Equipamento> builder)
    {
        builder.ToTable(LTE_dp_to_dp_Equipamento.TableName);
        builder.HasKey(x => x.EquipmentLoteId);
        // Define a 1:many relationship.
        builder.HasOne(x => x._Lote)
            .WithMany(x=> x._LoteEquipamentos)
            .HasForeignKey(x => x.LoteId)
            .OnDelete(DeleteBehavior.Restrict);
    }
    

    You don't need to define a one-to-one relationship between the LTE_dp_to_dp_Equipamento and the Equipment table as you can always access using the LTE_dp_to_dp table. If you feel that you need to, then you may need to revise your data structure otherwise it is simply a bad design.

    After migration your table structure should look as follows:

    One-To-One Relationship