TimeStamp Attribute in Entity Framework Core

SPONSOR AD

TimeStamp Attribute in Entity Framework Core (EF Core)

In this article, I will discuss TimeStamp Data Annotation Attribute in Entity Framework Core (EF Core) with Examples. Please read our previous article, which discussed DatabaseGenerated Attribute in Entity Framework Core with Examples.

TimeStamp Attribute in Entity Framework Core

In Entity Framework Core (EF Core), the Timestamp attribute is used to specify that a particular byte array property should be treated as a concurrency token. A concurrency token is a value used to ensure that the data being updated or deleted has not changed since it was last read, providing a way to manage concurrent operations on the data.

When using the Timestamp attribute, EF Core will configure the property as a RowVersion column in SQL Server (and analogous types in other databases). The database automatically updates the RowVersion every time a row is updated, which allows EF Core to detect concurrent edits.

The TimeStamp Data Annotation Attribute in Entity Framework Core can only be applied once to a byte array type property in an entity class. It creates a column with timestamp data type in the SQL Server database. EF Core will automatically use this Timestamp column in concurrency check on the UPDATE SQL Statement in the database. The TimeStamp attribute can only be applied to a byte[] array type property. It cannot be applied to other data types.

SPONSOR AD

If you go to the definition of TimeStamp Attribute in EF Core, you will see the following. As you can see, this class has a parameterless constructor.

TimeStamp Data Annotation Attribute in Entity Framework Core

So, using the Timestamp Attribute in Entity Framework Core is one of the ways to handle the Concurrency Issues. The Concurrency issue arises when multiple users or transactions attempt to update/delete the same row simultaneously. We can handle this Concurrency issue using the Timestamp column. TimeStamp can also be referred to as RowVersion.

What is TimeStamp or RowVersion in SQL Server?

In SQL Server, ROWVERSION and TIMESTAMP are Synonyms. The ROWVERSION has been available since SQL Server 2005, while TIMESTAMP is deprecated and will be removed in a future version of SQL Server.

ROWVERSION (TIMESTAMP) is an incrementing 8-byte binary number that does not store any DateTime-related information. In SQL Server, ROWVERSION and TIMESTAMP data types represent automatically generated unique binary numbers within the database. This helps us maintain the integrity of the database when multiple users are updating and deleting rows simultaneously.

Suppose a table contains a ROWVERSION (or TIMESTAMP) column. In that case, any time a new row is inserted or any existing row is updated, the value of the ROWVERSION (or TIMESTAMP) column is set to the current ROWVERSION (or TIMESTAMP) value. This is true even when an UPDATE statement does not change the data in the database.

Example to Understand Timestamp Attribute in EF Core:

Let us understand the Timestamp Attribute in Entity Framework Core with an example. Please modify the Student Entity class as follows. Here, we have applied the TimeStamp Attribute of the RowVersion Property, which is byte[] array type. In an Entity, we can only apply the TimeStamp Attribute once. The database will create the column with the TimeStamp data type.

SPONSOR AD
using System.ComponentModel.DataAnnotations;
namespace EFCoreCodeFirstDemo.Entities
{
    public class Student
    {
        public int StudentId { get; set; }
        public string? FirstName { get; set; }
        public string? LastName { get; set; }

        //We cannot have more than Column with [TimeStamp] Attribute
        //One Table Can have only one TimeStamp Column
        [Timestamp]
        public byte[]? RowVersion { get; set; }
    }
}

Next, modify the context class as follows:

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreCodeFirstDemo.Entities
{
    public class EFCoreDbContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //To Display the Generated the Database Script
            optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

            //Configuring the Connection String
            optionsBuilder.UseSqlServer(@"Server=LAPTOP-6P5NK25R\SQLSERVER2022DEV;Database=EFCoreDB;Trusted_Connection=True;TrustServerCertificate=True;");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        }

        public DbSet<Student> Students { get; set; }
    }
}

Next, modify the Main Method of the Program class as follows. Here, you can see we have added two entities to the database. The point that you need to remember is that the database autogenerates the TimeStamp value. So, we do not need to set the value for that property.

using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                using EFCoreDbContext context = new EFCoreDbContext();

                var student1 = new Student() { FirstName = "Pranaya", LastName = "Kumar" };
                context.Students.Add(student1);

                var student2 = new Student() { FirstName = "Hina", LastName = "Sharma" };
                context.Students.Add(student2);

                context.SaveChanges();
                Console.WriteLine("Students Added");

                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}"); ;
            }
        }
    }
}

Note: Before Proceeding further, let us delete the EFCoreDB database using SSMS and Migration folder from our project.

With the above changes, open the Package Manager Console and Execute the add-migration and update-database commands as follows. You can give any name to your migration. Here, I am giving EFCoreDBMig1. The name that you are giving it should not be given earlier.

TimeStamp Data Annotation Attribute in Entity Framework Core

If you verify the database, the RowVersion column should be created with the TimeStamp data type, as shown in the image below.

TimeStamp Data Annotation Attribute in EF Core

Now run the application and then verify the Students table. You will see that the auto-incrementing 8-byte binary number generated, unique within the database, will be stored in the RowVersion column, as shown in the image below.

SPONSOR AD

TimeStamp Data Annotation Attribute in EF Core

The timestamp column will be included in the where clause whenever we update an entity and call the SaveChanges method. We have already added two students with StudentId 1 and 2. Let us update the student’s first and last names, whose ID is 1. To do so, modify the Main Method of the Program class as follows.

using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                using EFCoreDbContext context = new EFCoreDbContext();

                //Fetch the Student Details whose Id is 1
                Student? studentId1 = context.Students.Find(1);
                
                if(studentId1 != null)
                {
                    studentId1.FirstName = "First Name Updated";
                    studentId1.LastName = "Last Name Updated";
                    context.SaveChanges();
                    Console.WriteLine("Student Updated");
                }
               
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}"); ;
            }
        }
    }
}

When you run the above code, you will get the following output. Here, you can see that the where clause and the Primary Key Column also use the RowVersion column within the UPDATE SQL Statement. This is to handle the concurrency issues.

TimeStamp Data Annotation Attribute in EF Core with Examples

If you verify the database, the RowVersion column should be updated with the latest increment value with Student ID 1, as shown in the image below.

TimeStamp Data Annotation Attribute in EF Core with Examples

Handling Concurrency Issues with EF Core using TimeStamp Attribute:

The primary use of the Timestamp attribute is for optimistic concurrency control. When an entity is updated, EF Core includes the RowVersion property in the WHERE clause of the update statement. If no rows are affected (indicating that the RowVersion has changed in the database since the entity was last fetched), EF Core throws a DbUpdateConcurrencyException. This means another user or process has modified the record, and you can handle this exception as appropriate for your application.

RowVersion (Timestamp) is a SQL column type that uses auto-generated unique binary numbers across that database. Any time a record is inserted or updated on a table with a row version, a new unique number is generated (in binary format) and given to that record. Again, the RowVersions are unique across that entire database, not just the table.

Handling Concurrency Issues with EF Core using TimeStamp Attribute

Let us understand how to handle concurrency issues with Entity Framework Core. Please modify the Main Method of the Program class as follows. Here, we are updating the same entity using two different threads simultaneously. Both Method1 and Method2 read the student entity whose ID is 1 and also read the same RowVersion value.

SPONSOR AD
using EFCoreCodeFirstDemo.Entities;
namespace EFCoreCodeFirstDemo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Main Method Started");
                Thread t1 = new Thread(Method1);
                Thread t2 = new Thread(Method2);
                t1.Start();
                t2.Start();
                t1.Join();
                t2.Join();
                Console.WriteLine("Main Method Completed");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}"); ;
            }
        }

        //USER 1 | Transaction 1
        public static void Method1()
        {
            using EFCoreDbContext context = new EFCoreDbContext();

            //Fetch the Student Details whose Id is 1
            var studentId1 = context.Students.Find(1);

            //Before Updating Delay the Thread by 2 Seconds
            Thread.Sleep(TimeSpan.FromSeconds(2));

            if (studentId1 != null)
            {
                studentId1.FirstName = studentId1.FirstName + "Method1";
                studentId1.LastName = studentId1.LastName + "Method1";
                context.SaveChanges();
                Console.WriteLine("Student Updated by Method1");
            }
        }

        //USER 2 | Transaction 2
        public static void Method2()
        {
            using EFCoreDbContext context = new EFCoreDbContext();

            //Fetch the Student Details whose Id is 1
            var studentId1 = context.Students.Find(1);

            //Before Updating Delay the Thread by 5 Seconds
            Thread.Sleep(TimeSpan.FromSeconds(5));

            if (studentId1 != null)
            {
                studentId1.FirstName = studentId1.FirstName + "Method2";
                studentId1.LastName = studentId1.LastName + "Method2";
                context.SaveChanges();
                Console.WriteLine("Student Updated by Method2");
            }
        }
    }
}

Let us assume Method 1 starts updating first. So, he will update the data in the database and the RowVersion value. Now, Method2 tries to update the same entity. If you remember, it will use the RowVersion column while updating, but that RowVersion value with the Method2 entity has already been modified by Method1. So, Method2 has a RowVersion value that no longer exists in the database; hence, the Method2 SaveChanges method will throw an exception showing concurrency issues.

TimeStamp Data Annotation Attribute in Entity Framework Core with Examples

If you verify the database, you will see the updated data with the updated RowVersion value for the student whose ID is 1, as shown in the image below. So, in this way, TimeStamp Attribute handles the concurrency issues. Each time you perform an INSERT or UPDATE Operation, the RowVersion value will increment.

TimeStamp Data Annotation Attribute in Entity Framework Core with Examples

Advantages and Disadvantages of TimeStamp Attribute in Entity Framework Core

The Timestamp attribute in Entity Framework Core (EF Core) designates a byte array property as a concurrency token. This token typically maps to the RowVersion column type in databases like SQL Server, and its primary purpose is to handle optimistic concurrency control. Let’s explore the advantages and disadvantages of using the Timestamp attribute in EF Core:

Advantages of TimeStamp Attribute in Entity Framework Core:
  • Optimistic Concurrency Control: The Timestamp attribute provides an automated way to manage optimistic concurrency, ensuring that data changes do not conflict with one another.
  • Automatic Database Management: When mapped to a RowVersion or equivalent column, the database automatically manages the updates of this column, ensuring a unique value for each modification.
  • Data Integrity: Helps prevent scenarios where one user unknowingly overwrites changes made by another user.
  • Performance: Offers a lightweight mechanism for concurrency control compared to tracking and comparing all individual properties of an entity.
  • Simplified Conflict Detection: When a concurrency conflict arises, EF Core throws a DbUpdateConcurrencyException, making detecting and handling these situations in the application logic relatively straightforward.
  • Less Boilerplate: By automating concurrency checks, developers can avoid manually writing code to handle these checks for each entity.
Disadvantages of TimeStamp Attribute in Entity Framework Core:
  • Additional Database Column: It introduces an extra column, which might not be desired in all scenarios, especially if the database is not under EF Core’s full control or if there are storage concerns.
  • Limited Scope: The Timestamp attribute is specifically for concurrency control. It cannot store actual timestamp data or other meaningful values.
  • Database Dependency: While RowVersion is a standard feature in SQL Server, the behavior or equivalent might differ across other databases. This can lead to potential issues if you plan to switch or support multiple database providers.
  • Error Handling Overhead: While DbUpdateConcurrencyException makes it easier to detect conflicts, developers must still write appropriate error-handling code to manage these exceptions and potentially present them in a user-friendly manner.
  • Potential Confusion: New developers or those unfamiliar with EF Core might find using the Timestamp attribute confusing, especially since it doesn’t store an actual timestamp.

While the Timestamp attribute provides a powerful and efficient way to handle optimistic concurrency in EF Core, it’s essential to understand its purpose and limitations. When used correctly, it can greatly enhance the integrity and consistency of data in scenarios with concurrent data access. However, it’s important to consider the specific requirements of your application and database to determine if it’s the right choice for your scenario.

In the next article, I will discuss ConcurrencyCheck Attribute in Entity Framework Core with Examples. In this article, I try to explain the TimeStamp Data Annotation Attribute in Entity Framework Core with Examples. I hope you enjoyed this TimeStamp Attribute in EF Core with Examples article.

SPONSOR AD

Leave a Reply

Your email address will not be published. Required fields are marked *