'How Working With Entity Framework Detached Objects?

hello community I have been trying to solve this problem for a long time, I am using blazor webassembly , dotnet core, and EntityFramework, I want to update the data of my person object, this object contains related data, neighborhood, municipality and state, if I update only the person object it works well if it updates, but if in the query I include the navigation properties with an inlcude it no longer updates the navigation properties

Error:

System.InvalidOperationException: The instance of entity type 'Status' cannot be tracked because another instance with the same key value for {'StatusId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

I have seen that there are already similar questions, but the truth is that I cannot find a solution for my problem.

I tried what the following blog says:

https://blog.maskalik.com/entity-framework/2013/12/23/entity-framework-updating-database-from-detached-objects/

but it didn't work quite right

 [HttpPut]
        public async Task<ActionResult> Put(Persona persona)
        {
            var user = await _userManager.GetUserAsync(HttpContext.User);
            
            var oldpersona = await context.Personas.FindAsync(persona.PersonaId);
           
            context.Entry(oldpersona).CurrentValues.SetValues(persona);
            context.Entry(oldpersona).CurrentValues.SetValues(persona.Colonia);
            context.Entry(oldpersona).CurrentValues.SetValues(persona.Municipio);
            context.Entry(oldpersona).CurrentValues.SetValues(persona.Estado);          

            await context.SaveChangesAsync(user.Id);
            return NoContent();

        }

I have read that I can use .AsNoTracking() to not track the entities but if I need to track them I am going to edit them.

this is my code in controller Person:

 [HttpGet("{id}")]
        public async Task<ActionResult<Persona>> Get(int id)
        {
            var persona = await context.Personas.Where(x => x.PersonaId == id)
               .Include(x => x.Colonia)
               .Include(x => x.Municipio)
               .Include(x => x.Estado)
               .FirstOrDefaultAsync();
            if (persona == null) { return NotFound(); }
            return persona;
        }

        [HttpPut]
        public async Task<ActionResult> Put(Persona persona)
        {
            context.Attach(persona).State = EntityState.Modified;
            await context.SaveChangesAsync();
            return NoContent();
        }

this is my related classes:

 public class Persona
    {
        [Key]
        public int PersonaId { get; set; }
        public string Nombre { get; set; }
        public string Ap_Paterno { get; set; }
        public string Ap_Materno { get; set; }
        public Sexo Sexo { get; set; }
        public DateTime? FechadeNacimiento {get; set;}
        public int Edad
        {
            get
            {
                DateTime now = DateTime.Today;

                int Edad = DateTime.Today.Year - FechadeNacimiento.Value.Year;

                if (DateTime.Today < FechadeNacimiento.Value.AddYears(Edad))
                    return --Edad;
                else
                    return Edad;
            }
        }
        public string Email { get; set; }
        public string Telefono { get; set; }
        public string Celular { get; set; }

        [StringLength(12)]
        public string Nss { get; set; }

        [StringLength(18)]
        public string Curp { get; set; }

        [StringLength(13)]        
        public string Rfc { get; set; }
        public string Observaciones { get; set; }
        public string Calle { get; set; }
        public string Num_Ext { get; set; }
        public string Num_Int { get; set; }
        public int CodigoPostal { get; set; }        
        public DateTime? FechaRegistro { get; set; }     
        public bool Activo { get; set; }     

        //propiedades de navegacion
        public int? ColoniaId { get; set; }
        public virtual Colonia Colonia { get; set; }
        public int? MunicipioId { get; set; }
        public virtual Municipio Municipio { get; set; }
        public int? EstadoId { get; set; }
        public virtual Estado Estado { get; set; }
    }   
    public enum Sexo
    {
        Masculino,
        Femenino,
        Indefinido
    }



public class Estado
    {
        [Key]
        public int EstadoId { get; set; }

        [Required(ErrorMessage = "El campo {0} es requerido")]
        public string Nombre { get; set; }
        public DateTime? FechaRegistro { get; set; }
        public bool Activo { get; set; }/

        //propiedades de navegacion        
        public int ZonaId{ get; set; }
        public virtual Zona Zona { get; set; }        
    }



public class Municipio
    {
        [Key]
        public int MunicipioId { get; set; }

        [Required(ErrorMessage = "El campo {0} es requerido")]
        public string Nombre { get; set; }
        public DateTime? FechaRegistro { get; set; }
        public bool Activo { get; set; }

        //propiedades de navegacion      
        public int EstadoId { get; set; }
        public virtual Estado Estado { get; set; }
    }

public class Colonia
    {
        [Key]
        public int ColoniaId { get; set; }

        [Required(ErrorMessage = "El campo {0} es requerido")]
        public int CodigoPostal { get; set; }
        public Asentamiento Asentamiento { get; set; }
        [Required(ErrorMessage = "El campo {0} es requerido")]
        public string NombreColonia { get; set; }       
        public DateTime? FechaRegistro { get; set; }
        public bool Activo { get; set; }

        //propiedades de navegacion       
        public int MunicipioId { get; set; }
        public virtual Municipio Municipio { get; set; }
        public int EstadoId { get; set; }
        public virtual Estado Estado { get; set; }
    }
    public enum Asentamiento
    {
        Colonia,
        Fraccionamiento,
        Condominio,
        [Display(Name = "Unidad habitacional")]
        Unidadhabitacional,
        Barrio,
        Equipamiento,
        [Display(Name = "Zona comercial")]
        Zonacomercial,
        Rancho,
        Rancheria,
        [Display(Name = "Parque industrial")]
        Parqueindustrial,
        Granja,
        Pueblo,
        Ejido,
        [Display(Name = " Zona federal")]
        Zonafederal,
        Aeropuerto,
        Hacienda,
        Paraje
    }

I guess the problem is that when I do the include, in municipality inside it brings state and colony inside it brings state and municipality, that's why EF tracks those entities more than once, but how can I solve it?

var person = await context.People.Where(x => x.PersonId == id) .Include(x => x.Colony) .Include(x => x.Municipality) .Include(x => x.Status) .FirstOrDefaultAsync();

There isn't a way to do something like this:

var person = await context.People.Where(x => x.PersonId == id) .Include(x => x.Colony) .Include(x => x.Municipality).ThenInclude(x=>x.State).AsNoTracking() .Include(x => x.Status) .FirstOrDefaultAsync(); if (person == null) { return NotFound(); } return person;

so that in the municipality it does not track the entity that is within it in this case state and does not interfere with the state include that I already have

what is the best way to edit entities with navigation properties ??

I have also seen that they use Detach but I really don't know how to use this in my code.

Entry<Persona>(persona).State = EntityState.Detached;


Solution 1:[1]

just try this

[HttpPut]
        public async Task<ActionResult> Put(Persona persona)
        {
             var existedPersona = await context.Personas.Where(x => x.PersonaId == persona.PersonaId)
                          .FirstOrDefaultAsync();
            if (existedPersona == null) { return NotFound(); }
            context.Entry(existedPersona).CurrentValues.SetValues(persona);
            await context.SaveChangesAsync();
            return NoContent();
        }

if you have some updates in navigation properties too, then include them

var existedPersona = await context.Personas.Where(x => x.PersonaId == id)
               .Include(x => x.Colonia)
               .Include(x => x.Municipio)
               .Include(x => x.Estado)
               .FirstOrDefaultAsync();

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Serge