1

I have an entity like this:

public class Player
{
    [Required]
    [Key]
    public int Id { get; set; }
    [Required]
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string NativeCountry { get; set; }
    [ConcurrencyCheck]
    public DateTime LastModified { get; set; }
    public virtual int TeamId { get; set; }

    //navigational property
    public virtual Team Team { get; set; }
    public virtual ICollection<Tournament> Tournaments { get; set; }
}

this is how i configure Player entity:

public PlayerConfiguration()
{
    Property(e => e.Id).IsRequired();
    Property(e => e.FirstName).IsRequired().IsConcurrencyToken(true);
    Property(e => e.NativeCountry).IsOptional();
    Property(e => e.LastModified).IsOptional().IsConcurrencyToken(true);

    HasRequired(e => e.Team).WithMany(s => s.Players).HasForeignKey(f => f.TeamId);
}

overridden OnModelCreating

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    Configuration.ValidateOnSaveEnabled = false;
    Configuration.LazyLoadingEnabled = true;

    modelBuilder.Configurations.Add(new PlayerConfiguration());
    modelBuilder.Configurations.Add(new TeamConfiguration());
    modelBuilder.Configurations.Add(new TournamentConfiguration());

    modelBuilder.Entity<Player>().ToTable("Player");
    modelBuilder.Entity<Team>().ToTable("Team");
    modelBuilder.Entity<Tournament>().ToTable("Tournament");

    base.OnModelCreating(modelBuilder);
}

somewhere I do this to update a player:

db.Entry<Player>(player).State = System.Data.EntityState.Modified;
try
{
    db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{

}

when I try to update any given player at the same time, using two browsers, nothing happens. I don't want to use a TimeStamp annotation, because it will cost me one extra column. How can I use the existing DateTime LastModified column to track concurrency. I even tried making FirstName (and others) as ConcurrencyToken, but again nothing happened. How does this [ConcurrencyCheck] work in asp.net web application.?? please help..

Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
Manish
  • 33
  • 1
  • 8
  • Did the value of the concurrency column for this entity change in the database? The way it works is that the value of the property that is a concurrency token is compared to the value in the database and if the values are the same it means that the entity in the database has not changed since you read it and therefore it is OK to save. On the other hand if the values don't match it means that the entity changed and you may overwrite changes. The reason why timestamp is ideal is that the database will modify the value each time the row is updated. – Pawel Jan 28 '12 at 12:05
  • Also note that if the value is not updated by the database you will always have a concurrency exception whenever you change the property value since it will not match what is in the database anymore. – Pawel Jan 28 '12 at 12:06
  • Pawel, i maintained the old (original) LastModified value in a static field, but didn't wrote a code to compare the original and current. I mean, that is the work of entity framework right? please help – Manish Jan 30 '12 at 11:41
  • Yes, Entity Framework should compare original and current for you. – Pawel Jan 30 '12 at 21:51

2 Answers2

2

It looks like your code doesn't change LastModified property so you are using still the same concurrency token. That is a reason why people use Timestamp column because timestamp is automatically handled by database and you don't have to deal with it.

To use concurrency token your entity must follow very strict rules.

  • Your entity must hold old value of concurrency token (in case of ASP.NET it requires round tripping concurrency token to client and receiving it back with modified data).
  • If you don't use database generated concurrency token (timestamp or row version) you must set it manually each time you are going to change the record
  • If you work with detached entities the new token can be set only ofter you attach the entity to the context otherwise you will get exception every time you try to save updated data

Here is sample code to validate that concurrency checking works:

class Program
{
    static void Main(string[] args)
    {
        using (var context = new Context())
        {
            context.Database.Delete();
            context.Database.CreateIfNotExists();

            context.Players.Add(new Player { FirstName = "ABC", LastName = "EFG", NativeCountry = "XYZ", LastModified = DateTime.Now});
            context.SaveChanges();
        }

        using (var context = new Context())
        {
            var player = context.Players.First();

            // Break here, go to database and change LastModified date
            player.LastModified = DateTime.Now;
            // If you changed LastModified date you will get an exception
            context.SaveChanges();
        }
    }
}
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • @Ladislav..i tried doing what you said, but it didn't work. I saved the original LastModified value in a static field and updated player.LastModified with it at the time of saving. But still nothing happened. I even made the LastModified column as byte[] and marked it TimeStamp, and it even gets autoincremented at each update but still nothing happens. If i have to compare the two values(LastModified in the static field and current LastModified in Db), then it wouldn't be very fruitful to me. same code(with appropriate modifications) works perfectly in ASP.NET MVC3, but not here – Manish Jan 30 '12 at 11:39
  • You cannot update it with original value. If you do that you will say there was no change. With timestamp the situation is more complicated in case of ASP.NET because you cannot set it directly from your code: http://stackoverflow.com/questions/5327649/entity-framework-optimistic-concurrency-exception-not-occuring/5327770#5327770 – Ladislav Mrnka Jan 30 '12 at 11:48
  • Ladislav Thanks for your help ..u helped me arrive at the solution. below is my post demonstrating how I achieved it. – Manish Jan 30 '12 at 13:16
0

this is what I did to make it work:

Player entity:

[ConcurrencyCheck]
public DateTime LastModified { get; set; }

in my code, onLoad of the form, called this function:

private static DateTime TimeStamp;
protected void LoadData()
    {
        Player player = new PlayerRepository().First(plyr => plyr.Id == Id);
        if (player != null)
        {   
           TimeStamp = player.LastModified;
            //rest of the code
        }
     }

and upon click of the save button i.e. while updating entity:

    protected void btnSave_Click(object sender, EventArgs e)
    {
        var playerRepo = new PlayerRepository();
        var teamRepo = new TeamRepository();

          if (Page.IsValid)
          {
            Player player;
            if (this.Id == 0)//add
            {
                player = new Player();
                //rest of the code
            }
            else //edit
              player = playerRepo.First(p => p.Id == Id);

            player.LastModified = DateTime.Now;

            //custom logic
            if (this.Id == 0) // add
            {
                playerRepo.Add(player);
                playerRepo.SaveChanges();
            }
            else // edit
            {
                try
                {
                    playerRepo.OriginalValue(player, "LastModified", TimeStamp);
                    playerRepo.SaveChanges();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                   //custom logic
                }

            }
      }

and OriginalValue in the AbstractRepository is this:

    public void OriginalValue(TEntity entity, string propertyName, dynamic value)
    {
        Context.Entry<TEntity>(entity).OriginalValues[propertyName] = value;
    }

so explicitly i had to change the OriginalValue of the ConcurrencyCheck marked column to the older one.

Manish
  • 33
  • 1
  • 8