2010-12-09 13 views
20

UPDATE (2010-12-21): Completamente reescribí esta pregunta en función de las pruebas que he estado haciendo. Además, solía ser una pregunta específica de POCO, pero resulta que mi pregunta no es necesariamente específica de POCO.OptimisticConcurrencyException no funciona en Entity Framework en ciertas situaciones

Estoy usando Entity Framework y tengo una columna de marca de tiempo en la tabla de mi base de datos que se debe usar para rastrear los cambios para una concurrencia optimista. Establecí el modo de concurrencia para esta propiedad en el Diseñador de Entidades en "Solucionado" y obtengo resultados inconsistentes. Aquí hay un par de escenarios simplificados que demuestran que la comprobación de simultaneidad funciona en un escenario pero no en otro.

con éxito tiros OptimisticConcurrencyException:

Si hay que adjuntar una entidad desconectada, entonces SaveChanges lanzará una OptimisticConcurrencyException si hay un conflicto de marca de tiempo:

[HttpPost] 
    public ActionResult Index(Person person) { 
     _context.People.Attach(person); 
     var state = _context.ObjectStateManager.GetObjectStateEntry(person); 
     state.ChangeState(System.Data.EntityState.Modified); 
     _context.SaveChanges(); 
     return RedirectToAction("Index"); 
    } 

no tirar OptimisticConcurrencyException:

Por otro lado, si recupero una nueva copia de mi entidad de la d atabase y hago una actualización parcial en algunos campos, y luego llamo SaveChanges(), a continuación, a pesar de que existe un conflicto de marca de tiempo, no consigo un OptimisticConcurrencyException:

[HttpPost] 
    public ActionResult Index(Person person) { 
     var currentPerson = _context.People.Where(x => x.Id == person.Id).First(); 
     currentPerson.Name = person.Name; 

     // currentPerson.VerColm == [0,0,0,0,0,0,15,167] 
     // person.VerColm == [0,0,0,0,0,0,15,166] 
     currentPerson.VerColm = person.VerColm; 

     // in POCO, currentPerson.VerColm == [0,0,0,0,0,0,15,166] 
     // in non-POCO, currentPerson.VerColm doesn't change and is still [0,0,0,0,0,0,15,167] 
     _context.SaveChanges(); 
     return RedirectToAction("Index"); 
    } 

Basado en SQL, parece que Entity Framework está ignorando el nuevo VerColm (que es la propiedad de marca de tiempo) y en su lugar utiliza el VerColm cargado originalmente. Debido a esto, nunca arrojará una OptimisticConcurrencyException.


ACTUALIZACIÓN: Adición de información adicional por la petición de Jan:

Tenga en cuenta que también añadí comentarios al código anterior para que coincida con lo que veo en mi acción del controlador mientras se trabaja a través de este ejemplo.

Este es el valor de la VerColm en mi base de datos antes de la actualización: 0x0000000000000FA7

Esto es lo que el Analizador de SQL muestra al hacer la actualización:

exec sp_executesql N'update [dbo].[People] 
set [Name] = @0 
where (([Id] = @1) and ([VerColm] = @2)) 
select [VerColm] 
from [dbo].[People] 
where @@ROWCOUNT > 0 and [Id] = @1',N'@0 nvarchar(50),@1 int,@2 binary(8)',@0=N'hello',@1=1,@2=0x0000000000000FA7 

Tenga en cuenta que @ 2 debería haber sido 0x0000000000000FA6 , pero es 0x0000000000000FA7

Aquí está el VerColm en mi base de datos después de la actualización: 0x0000000000000FA8


¿Alguien sabe cómo puedo evitar este problema? Me gustaría que Entity Framework arroje una excepción cuando actualizo una entidad existente y hay un conflicto de marca de tiempo.

Gracias

+0

Por favor, publique el código que hace el ahorro. –

+0

No puedo reproducir esto. Obtengo una OptimisticConcurrencyException cuando trato de guardar la entidad cargada y modificada con un conflicto de marca de tiempo. ¿Estás seguro de que tienes un conflicto de marca de tiempo? ¿Puedes publicar tu consulta SQL perfilada? – Jan

+0

Hola Jan, agregué información adicional arriba según tu solicitud. –

Respuesta

24

Explicación

La razón por la que no está recibiendo la OptimisticConcurrencyException esperado en su segundo ejemplo de código se debe a la concurrencia cheques de manera EF:

Cuando se recupera por entidades Al consultar su db, EF recuerda el valor de todos con ConcurrencyMode.Fixed propiedades marcadas en el momento de la consulta como los valores originales no modificados.

Luego, cambia algunas propiedades (incluidas las Fixed marcadas) y llama al SaveChanges() en tu DataContext.

EF comprueba las actualizaciones simultáneas comparando los valores actuales de todas las columnas Fixed db con los valores originales no modificados de las propiedades marcadas Fixed. El punto clave aquí es que EF trata la actualización de su propiedad timestamp como una actualización de propiedad de datos normal. El comportamiento que ves es por diseño.

Solución/Solución

Para solucionar dispone de las siguientes opciones:

  1. Use su primera aproximación: No vuelva a consultar la base de datos para su entidad, sino Fije la entidad recreado a su contexto .

  2. falso el valor de su marca de tiempo para ser el valor db actual, por lo que el cheque EF concurrencia utiliza su valor suministrado como se muestra a continuación (véase también this answer sobre una cuestión similar):

    var currentPerson = _context.People.Where(x => x.Id == person.Id).First(); 
    currentPerson.VerColm = person.VerColm; // set timestamp value 
    var ose = _context.ObjectStateManager.GetObjectStateEntry(currentPerson); 
    ose.AcceptChanges();  // pretend object is unchanged 
    currentPerson.Name = person.Name; // assign other data properties 
    _context.SaveChanges(); 
    
  3. Puede comprobar para la concurrencia a sí mismo mediante la comparación de su valor de marca de tiempo con el valor de marca de tiempo vuelve a consultar:

    var currentPerson = _context.People.Where(x => x.Id == person.Id).First(); 
    if (currentPerson.VerColm != person.VerColm) 
    { 
        throw new OptimisticConcurrencyException(); 
    } 
    currentPerson.Name = person.Name; // assign other data properties 
    _context.SaveChanges(); 
    
+0

Gracias por la explicación detallada. –

5

Aquí es otra ap proach que es un poco más genérico y cabe en la capa de datos:

// if any timestamps have changed, throw concurrency exception 
var changed = this.ChangeTracker.Entries<>() 
    .Any(x => !x.CurrentValues.GetValue<byte[]>("Timestamp").SequenceEqual(
     x.OriginalValues.GetValue<byte[]>("Timestamp"))); 
if (changed) throw new OptimisticConcurrencyException(); 
this.SaveChanges(); 

sólo chequea para ver si la marca de tiempo ha cambiado y lanza una excepción de concurrencia.

+0

Intenté esto y parece funcionar bien.Sin embargo, me gustaría lanzar una DbUpdateConcurrencyException similar a la EF, incluida la colección Entities, sin embargo, no existe un setter para esa colección. Traté de echar un vistazo a la fuente con ILSpy, pero eso no reveló nada útil sin vadear un montón de código (que no tengo tiempo de hacer). ¿Alguien que sepa cómo se podría hacer esto? – Anttu

+0

Nota: El código de ejemplo no funciona para las entidades eliminadas. Debería actualizarse a '... Any (x => x.State == EntityState.Modified &&! X.CurrentValues.GetValue <....' de lo contrario, se generará una InvalidOperationException si intentas emitir una eliminación. – Anttu

+0

He reelaborado esta idea para encajar en mi DAL, funciona muy bien y es simple. Gracias. – RitchieD

2

Si primero está el código EF, entonces use un código similar al código siguiente. Esto cambiará el TimeStamp original cargado desde db al de UI y se asegurará de que ocurra OptimisticConcurrencyEception.

db.Entry(request).OriginalValues["Timestamp"] = TimeStamp; 
+0

Eso es lo que estaba buscando, gracias. –

Cuestiones relacionadas