none
Error al hacer Update (EF6) RRS feed

  • Pregunta

  • Buenas, 

    Tengo la siguiente relación:

    Cabe señalar que User se corresponde con la clase proporcionada por Asp.Net Identity:

    En el Controller tengo lo siguiente:

    El método ObtenerPorIdAsync tiene la siguiente forma en el repositorio:

    En el servicio de dominio aplico Unit Of Work para guardar la información en las 3 tablas relacionadas:

    La excepción ocurre al ejecutarse el primer Update e indica lo siguiente:

    Attaching an entity of type 'Domain.Identity.Entities.UserRole' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

    El Update que se invoca es genérico y tiene la siguiente forma:

    public virtual void Update(TEntity item)
    {
    	databaseContext.Entry(item).State = EntityState.Modified;
    }

    No entiendo qué está pasando, esto estuvo funcionando hace algún tiempo. 







    • Editado eduar2083 jueves, 29 de agosto de 2019 17:28
    jueves, 29 de agosto de 2019 17:19

Todas las respuestas

  • hola

    Una duda, como unes la instancia de UoW, con la de los repositories que estas invocando en UpdateAsync() ?

    porque veo que usas un factory de uow, pero despues las instancias de los reposotories aparecen magicamente

    ----

    La entidad User tiene propiedades de navegacion a las demas entidades, por lo tanto cuando haces el Update() de User indicando el

    State = EntityState.Modified;

    tambien aplica a las relaciones que quizas debas marcarlas como EntityState.Unchanged, por eso no creo que puedas usar el generico, sino que deba ser algo concreto para el User

    public override void Update(User item)
    {
    	databaseContext.Entry(item).State = EntityState.Modified;
    	databaseContext.Entry(item.Roles).State = EntityState.Unchanged;
    }
    
    la idea es que redefinas para el User el repositorio generico

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    jueves, 29 de agosto de 2019 18:34
  • Leandro,

    En el servicio de Domino he comentado las 2 primeras líneas:

    public async override Task<int> UpdateAsync(User item)
    {
    	try
    	{
    		using (var uow = unitOfWorkFactory.Create())
    		{
    			//userProfileInfoRepository.Update(item.UserProfileInfo);
    			//passwordHistoryRepository.Update(item.Passwords);
    			userRepository.Update(item);
    
    			int r = await uow.CommitAsync();
    
    			return r;
    		}
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    He sobreescrito el Update para el repositorio de User:

    public override void Update(User item)
    {
    	databaseContext.Entry(item).State = EntityState.Modified;
    	databaseContext.Entry(item.UserProfileInfo).State = EntityState.Modified;
    	databaseContext.Entry(item.Roles).State = EntityState.Unchanged;
    	databaseContext.Entry(item.Passwords).State = EntityState.Unchanged;
    	//databaseContext.Entry(item.Logins).State = EntityState.Unchanged;
    	//databaseContext.Entry(item.Claims).State = EntityState.Unchanged;
    }

    Sin embargo, ocurre el mismo error indicando la tabla UserRole.

    Con respecto a Unit Of Work, lo tengo de la siguiente manera:

    La clase Factory:

    La clase UnitOfWorkIdentity:

    La clase UnitOfWork:

    public class UnitOfWork : IUnitOfWork
    {
    	private IDatabaseContext databaseContext;
    
    	public UnitOfWork(IDatabaseContext databaseContext)
    	{
    		this.databaseContext = databaseContext;
    	}
    	
    	#region Métodos Públicos
    	public int Commit()
    	{
    		return databaseContext.SaveChanges();
    	}
    
    	public async Task<int> CommitAsync()
    	{
    		return await databaseContext.SaveChangesAsync();
    	}
    	
    	public void Dispose()
    	{
    		try
    		{
    			if (databaseContext != null)
    			{
    				databaseContext.Dispose();
    				databaseContext = null;
    				GC.SuppressFinalize(this);
    			}
    		}
    		catch (Exception ex)
    		{
    			throw ex;
    		}
    	}
    	#endregion
    }

    Recuerdo que hace algún tiempo me basé en un tutorial que encontré en la web para implementar este patrón en una arquitectura orientada al dominio, díganme si está correctamente implementado o tendría que modificar.

    Saludos.






    • Editado eduar2083 viernes, 30 de agosto de 2019 22:59
    viernes, 30 de agosto de 2019 22:18
  • Hola, veo que debes usar el método que emplea el Tracking ya que estás actualizando luego la entidad. En la imagen de tu pregunta para tu controller usa ObtenerPorIdWithTrackingAsync() 

    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos

    viernes, 30 de agosto de 2019 23:28
  • Sergio,

    Ocurría el mismo error con o sin Tracking.

    He modificado el método de repositorio que obtiene User por Id (ambos con y sin Tracking) y sólo incluir las tablas User y UserProfileInfo ya que son las únicas que se ven afectadas en la operación de actualización que pretendo.

    public async Task<User> ObtenerPorIdWithTrackingAsync(int id)
    {
    	try
    	{
    		var user = await base.QueryableFiltered(t => t.Id == id)
    						 .Include(t => t.UserProfileInfo)
    						 //.Include(t => t.Roles)
    						 //.Include(t => t.Passwords)
    						 .FirstOrDefaultAsync();
    
    		// Encriptar información sensible
    		user.Encrypt = DefaultEncryptor.Encriptar(user.Id.ToString());
    
    		return user;
    	}
    	catch (Exception ex)
    	{
    		throw ex;
    	}
    }

    Con esto, ya no sería necesario hacer overrride del Update en el repositorio de User, sino al nivel de Servicio de dominio hacer:

    Al ejecutarse el CommitAsync() del objeto uow veo en el Sql Profiler que se ejecutan 2 updates:

    exec sp_executesql N'UPDATE [Seguridad].[User]
    SET [Email] = @0, [EmailConfirmed] = @1, [PasswordHash] = @2, [SecurityStamp] = @3, [PhoneNumber] = NULL, [PhoneNumberConfirmed] = @4, [TwoFactorEnabled] = @5, [LockoutEndDateUtc] = NULL, [LockoutEnabled] = @6, [AccessFailedCount] = @7, [UserName] = @8, [UsuarioCreacion] = @9, [FechaCreacion] = @10
    WHERE ([Id] = @11)
    ',N'@0 varchar(100),@1 bit,@2 varchar(128),@3 varchar(128),@4 bit,@5 bit,@6 bit,@7 int,@8 varchar(30),@9 varchar(30),@10 datetime2(7),@11 int',@0='JPEREZ741@GMAIL.COM',@1=1,@2='ALL+q7irEj4sPJyBHLmn/Zvd3CNqWucWtM7oKaXJVMPWRwGZSY7SBU9oumiI+qG7XA==',@3='5710509d-af72-44a8-b588-c32cc6f72d24',@4=0,@5=0,@6=0,@7=0,@8='ADMIN1',@9='SGC',@10='2019-08-28 20:29:35.5700000',@11=1
    exec sp_executesql N'UPDATE [Seguridad].[UserProfileInfo]
    SET [Names] = @0, [FirstName] = @1, [LastName] = @2, [Sex] = @3
    WHERE ([Id] = @4)
    ',N'@0 varchar(50),@1 varchar(50),@2 varchar(50),@3 bit,@4 int',@0='JUAN',@1='PEREZ',@2='TORRES',@3=0,@4=1

    ¿Esto me asegura que es una sola transacción? ¿Creo que no verdad?, 



    • Editado eduar2083 sábado, 31 de agosto de 2019 1:20
    sábado, 31 de agosto de 2019 1:14
  • Si, esa es la idea del patrón UoW. 

    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos

    sábado, 31 de agosto de 2019 2:09