How to update an existing entity that has a nested list of entities?

598 views Asked by At

I'm trying to update an entity using entity framework but, everytime I try to do it, it raises an error saying that a nested entity the main class contains cannot be tracked.

These are my classes:

 public abstract class BaseEntity
 {
     public int Id { get; set; }
 }
 public class Dashboard : BaseEntity
 {
     public int Order { get; set; }
     public string Title { get; set; }
     public bool Enabled { get; set; }
     public virtual ICollection<Submenu> Submenu { get; set; }
 }
public class Submenu : BaseEntity
{
    public int Order { get; set; }
    public bool Enabled { get; set; }
    public string Title { get; set; }
    public string Image { get; set; }
    public string Descriptions { get; set; }
    public virtual ICollection<Action> Actions { get; set; }
    public int DashboardId { get; set; }
    public virtual Dashboard Dashboard { get; set; }
}

public class Action : BaseEntity
{
    public string Type { get; set; }
    public string Label { get; set; }
    public string Url { get; set; }
    public string Extension { get; set; }

    public virtual Submenu Submenu { get; set; }
    public int SubmenuId { get; set; }
}

The one I am using to update is Dashboard, which contains the rest of the classes.

I'm trying to do it using a generic service layer and a generic repository that are defined this way:


public class GenericService<T> : IGenericService<T> where T : BaseEntity
{
    private readonly IBaseRepository<T> baseRepository;

    public GenericService(IBaseRepository<T> baseRepository)
    {
        this.baseRepository = baseRepository;
    }

    public async Task Update(T entity, T attachedEntity)
    {
        await baseRepository.Update(entity, attachedEntity);
    }
}

public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
{
    private readonly PortalContext dataContext;

    private DbSet<T> DbSet { get; set; }

    public BaseRepository(PortalContext context)
    {
        dataContext = context;
        DbSet = dataContext.Set<T>();
    }

    public async Task Update(T entity, T attachedEntity)
    {
        dataContext.Entry(attachedEntity).State = EntityState.Detached;
        DbSet.Attach(entity);
        dataContext.Entry(entity).State = EntityState.Modified;
        await dataContext.SaveChangesAsync();
    }
}

And, at last but no least, this is the way I am configuring everything at Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<PortalContext>(
        options => options.UseSqlServer(Configuration.GetConnectionString("PortalContext"))
    );
    
    services.AddTransient(typeof(IGenericService<>), typeof(GenericService<>));
    services.AddTransient(typeof(IBaseRepository<>), typeof(BaseRepository<>));
    
    services.AddTransient<Func<string, ClaimsPrincipal, IRoleCheck>>((serviceProvider) =>
    {
        return (controllerName, claimsPrincipal) =>
                new RoleCheck(serviceProvider.GetRequiredService<IGenericService<Dossier>>(),
                serviceProvider.GetRequiredService<IGenericService<DossierTemplate>>(),
                serviceProvider.GetRequiredService<IGenericService<Dashboard>>(),
                controllerName, claimsPrincipal);
    });
}

What the application first does is calling the RoleCheck class to retrieve and filter the required entities and, after that, the user can update them.

When I call the update function at the controller

 public async Task<ActionResult<Dashboard>> Put(int id, [FromBody] Dashboard dashboard)
 {
     var currentDashboard = await service.Get(id);
     if (currentDashboard == null)
     {
         return NotFound();
     }
    
     await service.Update(dashboard, currentDashboard);
     return Ok();

}

I always receive the next error at the repository: error

Is there something I am doing wrong? I have been stuck with this for a week now...

Thanks in advance and sorry for the long text, but I wanted it to be clear.

1

There are 1 answers

1
Adrián Domínguez On

I could finally solve it by adding .AsNoTracking() at the Get() method of my repository:

public async Task<T> Get(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> includes)
{
    IQueryable <T> query = DbSet.AsNoTracking();
    if (includes != null)
    {
        query = includes(query);
    }
    return await query.FirstOrDefaultAsync(m => m.Id == id);
}