Note that there are some explanatory texts on larger screens.

plurals
  1. POEntityFramework 4.3 issue when calling SaveChanges multiple times with Concurrency Check
    text
    copied!<p><em>-- Changed the scope of the question because this other piece of code is easier to explain but it's just the same behavior as the one that made me write the first version of the question...</em></p> <p>There is something strange with EF 4.3.1 Concurrency Check and the automatic caching when calling <code>SaveChanges()</code> more than once.</p> <p>When using EF 4.3 (with the hardcoded column type bug and all) the same piece of code works fine (probably because there were no concurrency check). When using 4.3.1 the second <code>SaveChanges()</code> call crashes as bellow.</p> <p>Here are the classes:</p> <pre><code>public class Concurrent { public byte[] Version { get; set; } } public class Operation : Concurrent { public virtual int CustomIdName { get; set; } public virtual C1 C1 { get; set; } public virtual C2 C2 { get; set; } // other properties } public class C1 : Concurrent { public virtual string CustomIdName { get; set; } public virtual ICollection&lt;C2&gt; C2 { get; set; } // other properties } public class C2 : Concurrent { public virtual string CustomIdName { get; set; } // other properties... } public class Repository : DbContext, IMyCustomGenericRepository { public void Save() { SaveChanges(); } public T Find&lt;T&gt;(object id) { return Set&lt;T&gt;().Find(id); } // other methods from the interface and all... public Repository Recreate() { return new Repository(); } } </code></pre> <p>Some coding...</p> <pre><code>public void TestMethod(IMyCustomGenericRepository Repository) { var operation = Repository.Find&lt;Operation&gt;(1); // ok operation.C1 = Repository.Find&lt;C1&gt;("1"); // ok operation.C2 = Repository.Find&lt;C2&gt;("1"); // ok Repository.Save(); // ok operation = Repository.Find&lt;Operation&gt;(1); // ok operation.C1 = Repository.Find&lt;C1&gt;("1"); // ok operation.C2 = Repository.Find&lt;C2&gt;("2"); // ok Repository.Save(); // fails } public void OtherTestMethod(IMyCustomGenericRepository Repository) { var operation = Repository.Find&lt;Operation&gt;(1); // ok operation.C1 = Repository.Find&lt;C1&gt;("1"); // ok operation.C2 = Repository.Find&lt;C2&gt;("1"); // ok Repository.Save(); // ok Repository = Repository.Recreate(); operation = Repository.Find&lt;Operation&gt;(1); // ok operation.C1 = Repository.Find&lt;C1&gt;("1"); // ok operation.C2 = Repository.Find&lt;C2&gt;("2"); // ok Repository.Save(); // ok } </code></pre> <p>My Fluent API looks like this:</p> <pre><code>modelBuilder.Entity&lt;Operation&gt;().HasKey(_o =&gt; _o.CustomIdName); modelBuilder.Entity&lt;Operation&gt;().Property(_o =&gt; _o.Version).HasColumnType("timestamp").IsConcurrencyToken(); modelBuilder.Entity&lt;C1&gt;().HasKey(_c1 =&gt; _c1.CustomIdName); modelBuilder.Entity&lt;C1&gt;().Property(_c1 =&gt; _c1.Version).HasColumnType("timestamp").IsConcurrencyToken(); modelBuilder.Entity&lt;C2&gt;().HasKey(_c2 =&gt; _c2.CustomIdName); modelBuilder.Entity&lt;C2&gt;().Property(_c2 =&gt; _c2.Version).HasColumnType("timestamp").IsConcurrencyToken(); </code></pre> <p>The only diference from the first method to the second is the call to <code>Repository.Recreate()</code> method which returns a whole new repository.</p> <h2>Update 2</h2> <p>Inside the Context class I've overritten the SaveChanges() method to this:</p> <pre><code>public override int SaveChanges() { // saves pending changes var _returnValue = base.SaveChanges(); // updates EF local entities (cache) foreach (var item in Set&lt;Operation&gt;().Local) (this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item); foreach (var item in Set&lt;C1&gt;().Local) (this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item); foreach (var item in Set&lt;C2&gt;().Local) (this as IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins, item); // returns the saving result return _returnValue; } </code></pre> <p>This piece of code <em>"solved"</em> the issue so it <strong>IS</strong> as I thought: SQL server automatically changes the TimeStamp value (concurrency token) but EF doesn't update that value on the property Version for some reason I still don't know.</p> <p>The problem is that with the wrong version I cannot save again simply because I'd violate the concurrency check from EF.</p> <p>Refreshing local entities (automatically cached entities by EF) prevents this issue <strong>BUT</strong> with a major side-effect: I'll have to manually refresh every changed entry. This isn't funny at all because:</p> <ol> <li>Detecting local entities (casts and other things)</li> <li>Iterating into every local entity and refreshing it's status</li> <li>If this is to be forgotten than the issue will happen again.</li> </ol> <p>Why EF does not automatically update the version colunm (set as <code>IsConcurrencyToken()</code>) just as it does with the PK column (that gets updated after insert if the PK is an identity column)?</p> <h2>Update 3 (possible solution?)</h2> <p>Overriding the SaveChanges() method from the DBContext and placing this code seems to get it all working quite fine:</p> <pre><code>public override int SaveChanges() { var entities = ChangeTracker.Entries().Where(_entry =&gt; _entry.State != System.Data.Entity.EntityState.Detached &amp;&amp; _entry.State != System.Data.Entity.EntityState.Unchanged &amp;&amp; _entry.State != System.Data.Entity.EntityState.Deleted).Select(_entry =&gt; _entry.Entity).ToArray(); if (!Configuration.AutoDetectChangesEnabled) ChangeTracker.DetectChanges(); var result = base.SaveChanges(); foreach (var entity in entities) (this as IObjectContextAdapter).ObjectContext.Refresh(System.Data.Entity.Core.Objects.RefreshMode.StoreWins, entity); return result; } </code></pre> <p>It's not a perfect solution. But it's automatic enought for now. Should do the trick for a while until a better answer shows up.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload