OptimisticConcurrencyException ne fonctionne pas dans Entity Framework dans certaines situations

asp.net c# concurrency entity-framework poco

Question

UPDATE (2010-12-21): Réécrivez complètement cette question en fonction des tests que j'ai effectués. De plus, c'était une question spécifique à POCO, mais il s'avère que ma question n'est pas nécessairement spécifique à POCO.

J'utilise Entity Framework et ma table de base de données contient une colonne timestamp qui doit être utilisée pour suivre les modifications afin d'obtenir une concurrence optimiste. J'ai défini le mode de concurrence pour cette propriété dans Entity Designer sur "Fixé" et j'obtiens des résultats incohérents. Voici quelques scénarios simplifiés démontrant que la vérification de la simultanéité fonctionne dans un scénario mais pas dans un autre.

Lève avec succès OptimisticConcurrencyException:

Si j'attache une entité déconnectée, SaveChanges lève une exception OptimisticConcurrencyException en cas de conflit d'horodatage:

    [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");
    }

Ne lève pas OptimisticConcurrencyException:

D'autre part, si je récupère une nouvelle copie de mon entité dans la base de données et que je fais une mise à jour partielle de certains champs, puis que j'appelle SaveChanges (), alors même s'il existe un conflit d'horodatage, je ne reçois pas d'exception 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");
    }

Basé sur SQL Profiler, il semble qu'Entity Framework ignore le nouveau VerColm (qui est la propriété timestamp) et utilise à la place le VerColm chargé à l'origine. Pour cette raison, il ne lancera jamais une exception OptimisticConcurrencyException.


UPDATE: Ajout d'informations supplémentaires à la demande de Jan:

Notez que j'ai également ajouté des commentaires au code ci-dessus pour coïncider avec ce que je vois dans l'action de mon contrôleur lorsque je travaille dans cet exemple.

Il s'agit de la valeur du VerColm dans ma base de données antérieure à la mise à jour: 0x0000000000000FA7.

Voici ce que montre SQL Profiler lors de la mise à jour:

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

Notez que @ 2 aurait dû être 0x0000000000000FA6, mais c'est 0x0000000000000FA7

Voici le VerColm dans ma base de données après la mise à jour: 0x0000000000000FA8


Est-ce que quelqu'un sait comment je peux contourner ce problème? J'aimerais que Entity Framework lève une exception lorsque je mets à jour une entité existante et qu'il y a un conflit d'horodatage.

Merci

Réponse acceptée

Explication

La raison pour laquelle vous ne recevez pas l'exception OptimisticConcurrencyException attendue sur votre deuxième exemple de code est due à la manière dont EF vérifie la simultanéité:

Lorsque vous récupérez des entités en interrogeant votre base de données, EF mémorise la valeur de toutes les propriétés marquées ConcurrencyMode.Fixed au moment de l'interrogation en tant que valeurs d'origine non modifiées.

Ensuite, vous modifiez certaines propriétés (y compris celles marquées Fixed ) et appelez SaveChanges() sur votre DataContext.

EF vérifie les mises à jour simultanées en comparant les valeurs actuelles de toutes les colonnes de la base de données marquée Fixed avec les valeurs d'origine non modifiées des propriétés marquées Fixed . Le point clé ici est que EF traite la mise à jour de votre propriété timestamp comme une mise à jour normale de la propriété de données. Le comportement que vous voyez est inhérent à la conception.

Solution / Contournement

Pour contourner ce problème, vous avez les options suivantes:

  1. Utilisez votre première approche: ne réinterrogez pas la base de données pour votre entité, mais attachez l’entité recréée à votre contexte.

  2. Faites de votre timestamp la valeur actuelle de la base de données, de sorte que le contrôle de simultanéité EF utilise la valeur fournie, comme indiqué ci-dessous (voir également la réponse à cette question):

    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. Vous pouvez vérifier vous-même la simultanéité en comparant la valeur de votre horodatage à la valeur de l'horodatage demandée:

    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();
    

Réponse populaire

Voici une autre approche un peu plus générique et adaptée à la couche de données:

// 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();

Il vérifie simplement si TimeStamp a été modifié et lève une exception de concurrence.



Related

Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow