Entity Framework e colonne Rowversion

Per gestire la concorrenza su una tabella in SQL Server è possibile aggiungere una colonna rowversion che permette di esporre una sequenza di numeri binari univoci generati automaticamente all’interno del database offrendo quindi meccanismo per contrassegnare le righe con un numero di versione.

Una colonna rowversion ha una dimensione di 8 byte e rappresenta un numero incrementale senza quindi avere riferimenti ad una data o ad un’ora, ogni database include un contatore che viene incrementato a ogni operazione di inserimento o aggiornamento eseguita su una tabella contenente una colonna di tipo rowversion e ogni tabella può includere una sola colonna di tipo rowversion.

Una colonna rowversion non è adatta per essere utilizzata come chiave o per essere indicizzata, dal momento che gli aggiornamenti eseguiti sulla riga modificano il suo valore, con la conseguenza che verrebbe modificato il valore della chiave.

Si noti che in SQL Server Management Studio (anche nella versione 2014) rowversion non è presente tra i tipi di dato per una colonna, ma è presente solo timestamp, questo perché come indicato nel seguente SQL 2008 Still Shows Timestamp, not RowVersion dal  SQL Programmability Team nel parser dell’engine ROWVERSION è un alias di TIMESTAMP (a riguardo si veda Data Type Synonyms (Transact-SQL)):

This is an issue in the engine since ROWVERSION is just an alias for TIMESTAMP in the Parser. The name “ROWVERSION” doesn’t make it through to the other parts of the engine like metadata or schema.

Umachandar, SQL Programmability Team

A riguardo si veda anche il seguente Deprecate TIMESTAMP (the keyword, not ROWVERSION itself):

We agree that the original naming of this type was unfortunate (and we can blame our predecessors ;-)). Especially since the name is used in the SQL standard for date/time datatypes.
We are going to look into how we can address this issue in a backwards-compatible way in the upcoming releases.
Please keep the votes coming.

Se in Visual Studio 2013 si utilizza Entity Framework 6.0 per creare un object-relational mapper verso una tabella contenente una colonna di tipo rowversion la proprietà ConcurrencyMode non viene impostata a Fixed, ma lasciata a None.

La proprietà ConcurrencyMode permette di gestire la concorrenza in quanto se impostata a Fixed su una colonna questa verrà controllata prima di eseguire operazioni di scrittura che verranno abortite se la colonna risulta modificata. A riguardo si veda Tip 19 – How to use Optimistic Concurrency with the Entity Framework.

Il problema è stata anche segnalato Reverse engineering does not mark rowversion/timestamp columns as concurrency tokens e verrà probabilmente sistemato nelle future versioni.

Ovviamente è possibile modificare manualmente l’impostazione, ma non è una soluzione ottimale soprattutto se il progetto è suscettibile di modifiche che implicano l’aggiornamento dell’object-relational mapper (file edmx) col rischio di dimenticarsene perdendo quindi la gestione della concorrenza.

Sempre nel thread di segnalazione  Reverse engineering does not mark rowversion/timestamp columns as concurrency tokens sono riportati dei riferimenti a tool che automatizzano l’impostazione della proprietà CorrencyManager intervenendo sul file Xml del object-relational mapper EDMX.

In particolare ho avuto modo di provare FixEFConcurrencyModes descritto nel post A utility to fix faulty ConcurrencyMode settings:

FixEFConcurrencyModes -i MyEdmxFile.edmx

-i <filename>               Edmx input file. Required.
-o <filename>               Output file. Default: overwrite the input file.
-n <pattern1 pattern2 …>  Regex patterns for column names used for concurrency control.
-t <type1 type2 …>        Types for concurrency control. Default: rowversion timestamp
-p                          Preview. Changes are shown but nothing is written to file.
-q                          Quiet mode.
–help                      Display the help screen.

Di seguito alcuni esempi di utilizzo:

  • Impostazione ConcurrencyMode = Fixed su tutte le colonne con nome RowVersion:
    FixEFConcurrencyModes -i myfile.edmx -n RowVersion
  • Impostazione ConcurrencyMode = Fixed su tutte le colonne di tipo uniqueidentifier or timestamp:
    FixEFConcurrencyModes -i MyEdmxFile.edmx -t uniqueidentifier timestamp
  • Impostazione ConcurrencyMode = Fixed su tutte le colonne il cui nome contiene la string “VersionNo” o termina con “Revision”:
    FixEFConcurrencyModes -i myfile.edmx -n VersionNo Revision$
  • Per avere una preview delle modifiche cheil tool apporta senza però applicarle è possibile utilizzare il parametro -p:
    FixEFConcurrencyModes -i myfile.edmx -p