Ho un Entity Framework Project con diverse entità collegate. Poiché è utilizzato da più utenti contemporaneamente, ho impostato un campo RowVersion per le entità che potrebbero essere modificate da più utenti contemporaneamente. Purtroppo ora ottengo una OptimisticConecurrencyException
ogni volta che provo a salvare una nuova entità, che è collegata a un'entità già esistente.
L'aggiornamento dell'archivio, l'inserimento o l'eliminazione dell'archivio hanno interessato un numero imprevisto di righe (0). Le entità potrebbero essere state modificate o eliminate da quando le entità sono state caricate. Vedere http://go.microsoft.com/fwlink/?LinkId=472540 per informazioni sulla comprensione e gestione delle eccezioni di concorrenza ottimistiche.
Il problema ora è che questo errore non fornisce alcuna indicazione su dove si trovi effettivamente l'errore. Potrebbe essere il modello sottostante che viene modificato nel frattempo, potrebbe esserci un errore di convalida sul nuovo modello o qualcos'altro.
Il codice che uso per aggiungere la nuova entità è il seguente:
using (ctx = new DbContext())
{
try
{
ctx.Samples.Add(model);
ctx.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
LogManager.HandleException(ex.InnerException);
}
}
model
è il modello che voglio aggiungere al database
Modifica: come visto sopra ho modificato il codice per ignorare l'aggiornamento di un modello sottostante. Inoltre ho verificato attraverso:
ctx.Database.log = s => Debug.Write(s);
Solo una dichiarazione di inserimento viene inviata al database e non un'istruzione di aggiornamento aggiuntiva.
INSERT [dbo].[Samples]([IDSample], [ModificationDate], [IDUser])
VALUES (@0, @1, @2)
SELECT [RowVersion]
FROM [dbo].[Samples]
WHERE @@ROWCOUNT > 0 AND [IDSample] = @0 AND [ModificationDate] = @1
Comprenderei l'eccezione se aggiornassi un'entità e la colonna rowversion non corrispondesse, ma in questo caso è un'entità completamente nuova. C'è un modo per vedere se una delle proprietà è malformata?
Edit2:
Invece di tagliare i millisecondi ora ho usato DateTime.Today invece di DateTime.Ora funziona. Apparentemente c'è qualche problema con datetime2 (4) su ModificationDate. Ho già fatto in modo che ModificationDate sia troncato a 4 millisecondi quindi non ci dovrebbero essere errori di analisi.
Edit3:
Dopo il passaggio a DateTime.Ora e il taglio dei millisecondi, ha smesso di funzionare e le entità non sono più inserite nel database. Ciò potrebbe essere causato dal fatto che il server sql ha problemi nell'abbinamento delle entità in base a valori millisecondi. Ho eseguito l'SQL generato da EF come visto sopra con alcuni valori fittizi ed è andato avanti anche se in alcune occasioni la query non ha restituito un valore di rowversion. In termini di struttura dell'entità, il client interpreterà questo come un valore di ritorno di 0 righe e quindi chiamerà un'eccezione di concorrenza. (Va anche notato che il ModificationDate insieme all'IDSample è la chiave primaria dell'entità.)
Edit4:
Ora sto usando DateTime.Today e poi aggiungo la precisione necessaria, che funziona per me. Questo può essere contrassegnato come risolto. (Anche se mi sarei aspettato che EF potesse occuparsi da solo della conversione del formato data / ora: /)
La domanda che ho è dove sono / stavi aggiungendo il DateTime? Stai creando troppi passaggi per risolvere questo problema. Creare un datetime, modificarlo, ecc.
Se l'entità si eredita da una classe base con proprietà mappate, aggiungere / aggiornare la concorrenza nell'override DbContext di SaveChanges ().
Ecco un esempio: (scritto senza sintassi ottimizzata)
public abstract class EntityBase
{
public int Id {get; set;}
public DateTime CreationDate {get; set;}
public DateTime? ModifyDate {get; set;}
public string VersionHash {get; set;}
}
public static class EntityBaseExtensions
{
public static void MyBaseEntityMapping<T>(this EntityTypeConfiguration<T> configuration) where T : EntityBase
{
configuration.HasKey(x => x.Id);
configuration.Property(x => x.CreationDate)
.IsRequired();
configuration.Property(x => x.ModifyDate)
.IsOptional();
configuration.Property(x => x.VersionHash).IsConcurrencyToken();
}
}
public class MyEntity : EntityBase
{
public string MyProperty {get; set;}
}
public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
public MyEntityMapping()
{
this.MyBaseEntityMapping();
Property(x=>x.MyProperty).IsRequired();
}
}
public class MyContext : DbContext
{
....
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges(); //this forces EF to compare changes to originals including references and one to many relationships, I'm in the habit of doing this.
var context = ((IObjectContextAdapter)this).ObjectContext; //grab the underlying context
var ostateEntries = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added); // grab the entity entries (add/remove, queried) in the current context
var stateEntries = ostateEntries.Where(x => x.IsRelationship == false && x.Entity is EntityBase); // don't care about relationships, but has to inherit from EntityBase
var time = DateTime.Now; //getting a date for our auditing dates
foreach (var entry in stateEntries)
{
var entity = entry.Entity as EntityBase;
if (entity != null) //redundant, but resharper still yells at you :)
{
if (entry.State == EntityState.Added) //could also look at Id field > 0, but this is safe enough
{
entity.CreationDate = time;
}
entity.ModifyDate = time;
entity.VersionHash = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10); //this an example of a simple random configuration of letters/numbers.. since the query on sql server is primarily using the primary key index, you can use whatever you want without worrying about query execution.. just don't query on the version itself!
}
}
return base.SaveChanges();
}
....
}