What is Identity Cache?
DataContext object uses identity cache to manage objects.
In this example, we are retrieving two students from the database with the same identity and using the same datacontext object. With the first LINQ query is executed, it gets translated to T-SQL. The SQL query is executed and the result is brought back into the application, where a Student object is created. The object identity is stored in the Identity cache. When the second LINQ query is issued for the same student object, LINQ checks the identity cache and returns a reference to the student object that already exists. Notice that there is only one call to the database, despite having two LINQ queries. S1 and S2 are pointing to the same student object in memory.
- using(SampleDataContext dbContext = new SampleDataContext()) {
- dbContext.Log = Console.Out;
- Student S1 = dbContext.Students.FirstOrDefault(x => x.ID == 1);
- Student S2 = dbContext.Students.FirstOrDefault(x => x.ID == 1);
- Console.WriteLine("S1 == S2 : {0}", object.ReferenceEquals(S1, S2));
- }
Code Explanation
First, we create an instance of a datacontext class.dbcontext.log which is going to log the SQL statements into the console screen. Notice we have two identical LINQ queries, and both are going to return the same student object where ID equals 1.
Create an Empty Console Application add LINQ to SQL designer class file and drag and drop the student table and paste the above code in the main method. Now let’s check the output :
Now notice that we have one T-SQL query which is fired to the database.
S1 == S2 which is True.
Each instance of LINQ to SQL DataContext class has its own identity cache. This means if we have 2 different DataContext instances, and when we issue two LINQ queries to retrieve a student with the same identity, we get 2 different student objects back. S1 and S2 are two different objects in memory and registered in two different identity maps. Notice that the database also gets called 2 times.
- using(SampleDataContext dbContext1 = new SampleDataContext())
- using(SampleDataContext dbContext2 = new SampleDataContext()) {
- dbContext1.Log = Console.Out;
- dbContext2.Log = Console.Out;
- Student S1 = dbContext1.Students.FirstOrDefault(x => x.ID == 1);
- Student S2 = dbContext2.Students.FirstOrDefault(x => x.ID == 1);
- Console.WriteLine("S1 == S2 : {0}", object.ReferenceEquals(S1, S2));
- }
The above code which we had created has two instances of SampleDataContext which is dbcontext1 and dbcontext 2. The next line which we have is going to Log the generated SQL of both the datacontext objects to the console screen. Again, we are requesting the same student object with their same ID but through different datacontext objects which are datacontext1 and datacontext2.
And we know that each instance of datacontext class has its own identity cache. That means when the query is issued it will fire that query to the database where we had stored the identity cache of the first dbcontext class and when the second query is issued we don’t have the student object so LINQ to SQL is going to fire another DB query and store the identity of that student object within the identity cache of the datacontext object.
So, in this case we will have two database queries fired and these two reference variables will now be pointing to two different students objects. It will be returned as false.
S1 == S2 False because S1 and S2 are pointing to two different student objects in the memory.
Now copy the above code and paste it in the main method
In this example, we changed the FirstName of the student using the first datacontext object. We then called SubmitChanges() method, so the database has stored the new name for this student. However, the student object for datacontext 2 still has the old name, because when we reissue the LINQ query for the same student object, this object is retrieved from the identity cache and not from the database. To have data retrieved from the database and to refresh the cache with updated values, we need to call the Refresh method of the second datacontext object.
- using(SampleDataContext dbContext1 = new SampleDataContext())
- using(SampleDataContext dbContext2 = new SampleDataContext()) {
- dbContext1.Log = Console.Out;
- dbContext2.Log = Console.Out;
- Student S1 = dbContext1.Students.FirstOrDefault(x => x.ID == 1);
- Student S2 = dbContext2.Students.FirstOrDefault(x => x.ID == 1);
- Console.WriteLine("S1.FirstName = {0}", S1.FirstName);
- Console.WriteLine("S2.FirstName = {0}", S2.FirstName);
- S1.FirstName = "Updated.....";
- dbContext1.SubmitChanges();
- Console.WriteLine("FirstName updated.....");
- S2 = dbContext2.Students.FirstOrDefault(x => x.ID == 1);
- Console.WriteLine("S1.FirstName = {0}", S1.FirstName);
- Console.WriteLine("S2.FirstName = {0}", S2.FirstName);
- dbContext2.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, S2);
- Console.WriteLine("After calling the refresh method");
- Console.WriteLine("S1.FirstName = {0}", S1.FirstName);
- Console.WriteLine("S2.FirstName = {0}", S2.FirstName);
- }
Now let’s try to understand the code line by line here. Now, the student name is Akshay
Run the app
Press F10
Now at this point there should be two queries fired.
One query is of dbcontext1 and other is of dbcontext2 and then it prints first name as Akshay because it’s the same student
What we are doing is updating the student firstname with S1 object.So let’s do that press F10 twice .
So now we should have the update query issued
The first name of student with ID 1 is updated.
Now go back and press F10 twice
Now look at this -- after we had submitted changes we had reloaded student 2 using dbcontext2 object. After that we are printing first name and last name:
After the update statement, we don’t have any selected query fired to the database --why is that? We need to understand that because dbcontext2 object is firing the same query already so at that point we retrieve student object with identity 1.
The identity of that object is stored in the identity of dbcontext2.
And when we fired this query again,
- S2 = dbContext2.Students.FirstOrDefault(x => x.ID == 1);
LINQ to SQL is going to check for the identity cache; since, we already have the object with the old value it’s still going to load that object from the cache and that's the reason we don’t have the select query .
Even after executing the first query S2, FirstName still shows the old value. Now we don’t want such behavior so we had refreshed the data which is stored in the cache and to do that we had used this refresh method
- dbContext2.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, S2);
So what is this Refresh method going to do? First of all we are going to Overwrite the current value of the S2 object .
When we call this refresh method the datacontext object is going to reissue the query and retrieve the data from the database and overwrite whatever values are present in the cache.
Press F10 again and go back now
We called this refresh method and there is another select query issued. Look at the output we have here: Firstname of S1 and Firstname of S2 shows the updated values.
Recommended Reads
- http://www.c-sharpcorner.com/article/introduction-to-linq/
- http://www.c-sharpcorner.com/article/how-to-use-linq-to-sql-for-retrieving-data-from-sql-server-database/
- http://www.c-sharpcorner.com/article/crud-operations-using-linq-to-sql/
- http://www.c-sharpcorner.com/article/how-to-view-linq-to-sql-generated-sql-queries-and-using-stored-procedures-with-l/
- http://www.c-sharpcorner.com/article/stored-procedure-with-output-parameters-in-linq-to-sql-and-what-is-sqlmetal/
- http://www.c-sharpcorner.com/article/lazy-loading-and-eager-loading-in-linq-to-sql/
- http://www.c-sharpcorner.com/article/single-table-inheritance-in-linq-to-sql/
References for this article
- https://msdn.microsoft.com/en-us/library/dd627203(v=vs.100).aspx