0

I have a situation where i have a counter field in a table named Profile, and on form submit, i will retrieve the counter field and +1 to the counter and update profile table. The incremented counter will be stored in a variable where i will then use to create new records in another table [Bidder]. The problem is when there are multiple form submit at the same time, duplicate record values will be created in the Bidder table

Profile profile = db.Profile.Where(w => w.TenderId == tender_Id && w.IsDeleted == false).FirstOrDefault();
int submission = profile.TotalSubmission + 1;

if (profile != null) {
    profile.TotalSubmission = submission;
    profile.ModifiedBy = user_id;
    profile.ModifiedOn = DateTime.Now;
    db.Entry(profile).State = EntityState.Modified;
    db.SaveChanges();
}

bid.ROId = string.Format("RO{0}", submission);
db.Entry(bid).State = EntityState.Modified;
db.SaveChanges();

How do i prevent duplicate ROId to be created?

Sefe
  • 13,731
  • 5
  • 42
  • 55
KylE
  • 5
  • 4
  • 2
    Create a unique index on the column. Or better, use an identity column – Rhumborl Sep 11 '17 at 10:44
  • @Rhumborl, to which table ? Profile or Bidder? – KylE Sep 11 '17 at 10:51
  • 1
    You could surround the code with a "lock", which would directly solve your problem without change very much code. As @Rhumborl said though, I would suggest looking at a difference approach for creating a unique id, by using the database to generate the key (i.e. if SQL Server an identity column). – Gary Holland Sep 11 '17 at 10:58
  • 1
    @GaryHolland: A lock will do nothing if the code runs on multiple machines. Also, on the same machine this block of code can only run once while the other threads are locked out. This can generate a massive performance issue, since you have an entire DB roundtrip during the lock. – Sefe Sep 11 '17 at 11:11
  • `if (profile != null) {` is useless. At this point, if `profile` was `null`, an exception would have been thrown at the previous line. – Tsahi Asher Sep 11 '17 at 11:20
  • @GaryHolland using a lock won't prevent other instances of your app from violating uniqueness due to race conditions. – Danny Varod Sep 11 '17 at 11:32
  • You are using this for an ID (`bid.ROId`)? As in a key field? You really should be using an identity column, or a sequence (if available). – Bradley Uffner Sep 11 '17 at 13:53

3 Answers3

0

You can not rely only on entity framework for your solution. Only the database has a full picture of the stored data. Your different entity context instances don't even know if other instances exist, so coordinating sequence numbers on a global scale is extremely difficult on EF level.

Depending on the frequency of conflicts, two options come to my mind to enforce the uniqueness of the sequence number:

  1. Unique constraint
  2. Stored procedure for writing the data

Unique constraint

You can create a UNIQUE constraint over the ProfileId and Sequence columns. When you store the data with a duplicate sequence number, you will get an exception. Either the exception itself or one of its inner exceptions will be an SqlException. You can examine the error number of that exception and if it's error number 2627 (if your DBMS is SQL Server; if it is not, check for the similar error in your DBMS), you know it's a unique key constraint violation. In this case you get the current sequence number from the DB and write the data again with a new sequence. You have to repeat that until the insert was successful.

In case you're using SQL server, you can selectively handle a UNIQUE KEY constraint violation like this (using C# 6.0 exception filters):

private bool IsUniqueKeyViolation(Exception exception) {
    Exception currentException = exception;
    while (currentException != null) {
        SqlException sqlException = exception as SqlException;
        if (sqlException != null) {
            return sqlException.Errors.Cast<SqlError>().Any(error => error.Number == 2627);
        }
        currentException = currentException.InnerException;
    }
    return false;
}

//...

//...Code to set up the POCOs before Save...
while(true) {
    try {
        context.SaveChanges();
    }
    catch(Exception exc) when (IsUniqueKeyViolation(exc)) {
        //...Code to update the sequence number...
        continue;
    }
    break;
}

This solution is only practical if the number of conflicts is expected to be small. If the number of conflicts is large, you will see a lot of unsuccessful UPDATE requests to the DB, which can become a performance issue.

EDIT:

As some other answers suggested, you could also use optimistic concurrency with a timestamp column. As long as you only update the DB from your own code, this works fine. However, a UNIQUE KEY constraint will protect the integrity of your data also from changes that don't originate from your application (like migration scripts etc.). Optimistic concurrency does not give you the same guarantee.

Stored procedure

You can create a stored procedure that will set the new sequence number from the last existing number in the same INSERT or UPDATE statement. The stored procedure can return the new sequence number back to the client and you can process it accordingly.

Since this solution will always update the DB in a single statement, it works well for a larger amount of conflicting updates. The disadvantage is that you have to write a part of your program logic in SQL on the DB level.

Sefe
  • 13,731
  • 5
  • 42
  • 55
  • This is incorrect - Entity Framework can create unique indexes via Code First. Also, if you are working with DDD, then the DB is completely controlled by your app and the app is the one with the full picture - not the database. – Danny Varod Sep 11 '17 at 11:30
  • Huh? So the DB cares if the unique index/key is created with code or DB first? And how does a design approach change how the DB works? You're talking design time for a run time problem. – Sefe Sep 11 '17 at 11:35
  • My issue is with the statement `You can not rely only on entity framework for your solution`, since you can solve this via EF using code first. – Danny Varod Sep 11 '17 at 13:31
  • 1
    @DannyVarod The statement is correct. EF has no provisions to ensure that the index will be unique. That can be only handled by the DB. It does not matter if you use EF code first to create a unique constraint. The constraint is handled by the DB, not EF. The only reason for the downvote is to bump your question up. – Sefe Sep 11 '17 at 13:35
  • I never vote down unless I think an answer is misleading or wrong, in fact I often vote up other answers, even if I think mine is better. I only answered because I disagreed with your answer - I don't lact rep. – Danny Varod Sep 20 '17 at 14:24
0

The uniqueness should be enforced using a unique index or a unique constraint.

You can create these using code first (from MSDN):

public class User
{
    public int UserId { get; set; }

    [Index(IsUnique = true)]
    public string Username { get; set; }

    public string DisplayName { get; set; }
}

or directly via the database.

The counter should be protected using optimistic concurrency:

public class MyEntity
{
    [Key]
    public Guid Id { get; set; }

    // Add a timestamp property to your class
    [Timestamp]
    [Required]
    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    [ConcurrencyCheck]
    public byte[] VersionTimestamp { get; set; }

    public int Counter { get; set; }
}

If you try to update the row with the VersionTimestamp after it has been changed without re-reading it from the database, you'll get an OptimisiticConcurrencyException e.g. in this test scenario:

// Read the entity
MyEntity entity;
using (var context = new MyContext())
{
    entity = context.MyEntities.Single(e => e.Id == id1);
}

// Read and update the entity
using (var context = new MyContext())
{
    var entity2 = context.MyEntities.Single(e => e.Id == id1);
    entity2.Counter++;
    context.SaveChanges();
}

// Try to update stale data
// - an OptimisticConcurrencyException will be thrown
using (var context = new MyContext())
{
    entity.Counter++;
    context.SaveChanges();
}
Danny Varod
  • 17,324
  • 5
  • 69
  • 111
0

If you are using SQL Server 2012 or newer, you can use a Sequence to accomplish this. You would also want to enforce uniqueness through a unique constraint.

public partial class YourEfContext : DbContext 
{
    .... (other EF stuff) ......

    // get your EF context
    public int GetNextSequenceValue()
    {
        var rawQuery = Database.SqlQuery<int>("SELECT NEXT VALUE FOR dbo.SomeSequence;");
        var task = rawQuery.SingleAsync();
        int nextVal = task.Result;

        return nextVal;
    }
}

Another option, if you don't have a version that supports sequences, is to use a stored procedure on the database to issue Id numbers. The stored proc can work in conjunction with an ID table, which it can place an explicit lock on. This means you can request an id from the proc, it can lock the table, read the current number, increment it, store it back in the table, release the lock, and return the id. You would need to call your proc from code to get the new id to assign. The lock on the db side ensures that you are only ever assigned unique values. As long as your id column is only ever given a value assigned by the proc, you will have unique values. You will still be able to assign arbitrary numbers though, which could include duplicates, that can be solved with a unique constraint.

None of this in Entity-Framework specific, though you can still access all this through entity-framework in one way or another.

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76