DEV Community

Cover image for EF: When SaveChanges does not save changes
Ahmad Ibrahim
Ahmad Ibrahim

Posted on

EF: When SaveChanges does not save changes

I was debugging an issue with DbContext.SaveChanges which was neither saving changes to the database nor throwing any exceptions, and after solving the issue I found it interesting to list some of the reasons that might cause this issue and how to detect them.
The possible reasons fall into four main categories.

  1. Wrong destination
  2. SaveChanges isn't called properly
  3. Changes are not tracked
  4. Database does not preserve changes

Wrong destination

You might be expecting your changes to be saved somewhere but your DbContext is saving it somewhere else. For example the connection string is not the one you expect. It's a common mistake that some developers change the connection string in the configuration file of the data access project instead of the startup project.

To check which connection string your DbContext is using you can do this.


 csharp
// check the value of the connection string
string connection = dbContext.Database.GetConnectionString();
dbContext.SaveChanges();


Enter fullscreen mode Exit fullscreen mode

Another reason could be that you are checking the wrong database table, maybe your EF entity is mapped to another table (e.g. via some fluent API configuration), you can check that as follows.


 csharp
var mapping = dbContext.Model.FindEntityType(typeof(YourEntity));
string schema = mapping.GetSchema();
string table = mapping.GetTableName();

dbContext.SaveChanges();


Enter fullscreen mode Exit fullscreen mode

Make sure that schema and table are as you expect.

SaveChanges isn't called properly

You might be calling SaveChanges on a different instance of your DbContext, and that might not be easy to spot in some complex codebases where changes to the DbContext are far from the call to SaveChanges.
EF assigns a unique ID for each DbContext instance which could be used to check that.


 csharp
dbContext.Employees.Add(newEmp);
string id1 = dbContext.ContextId;

.....

// elsewhere in the codebase
string id2 = dbContext.ContextId;
dbContext.SaveChanges();


Enter fullscreen mode Exit fullscreen mode

You have to confirm that id1 equals id2.

Another silly mistake might be calling SaveChangeAsync without awaiting it (i.e. using await keyword) which absorbs exceptions and obviously to fix that you just need to await the call await dbContext.SaveChangesAsync();.

Changes are not tracked

The EF Change Tracker is the component responsible for detecting the changes that should be updated in the database. There are many reasons why change tracker doesn't see/consider your changes. To check if this is the case you might do something like this.


 csharp
bool hasChanges = dbContext.ChangeTracker.HasChanges(); // should be true
int updates = dbContext.SaveChanges();                  // should be > 0


Enter fullscreen mode Exit fullscreen mode

When hasChanges is false or updates is 0 then the Change Tracker couldn't detect your changes, maybe because you're changing the value of a NotMapped property like FullName in the entity below.


 csharp
public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [NotMapped]
    public string FullName { get; set; }
}


Enter fullscreen mode Exit fullscreen mode

Or maybe you are changing a disconnected/detached entity which is a common scenario in web applications when you receive an object from the client-side, one way to fix this is by changing the entry state.


 csharp
db.Entry(myEntity).State = EntityState.Modified; // use an appropriate state
dbContext.SaveChanges();


Enter fullscreen mode Exit fullscreen mode

Database not preserving changes

One of the sneakiest issues is when you begin a database transaction but you don't commit it, it's sneaky because in this case you will see that the Change Tracker detected your changes (i.e. hasChanges equals true, updates will be greater than 0) and the SQL Server Profiler will show that the insert statement executed successfully on your database but still you won't find any changes, check this.


 csharp
using (var context = new MyContext())
{
    var transaction = context.Database.BeginTransaction(IsolationLevel.Serializable);
    context.Employees.Add(newEmployee);
    int updates = context.SaveChanges();   // updates == 1 even when the transaction is not committed
}


Enter fullscreen mode Exit fullscreen mode

In my case that was the reason why SaveChange wasn't working and the BeginTransaction call was not as clear as in the above snippet, it was buried into a forsaken area in the codebase, where someone was trying something and forgot to remove it.
To detect whether your DbContext has a current transaction you can check if context.Database.CurrentTransaction is not null.


 csharp
// use this to check if a database transaction has begun
var hasTransaction = context.Database.CurrentTransaction != null;
context.SaveChanges();


Enter fullscreen mode Exit fullscreen mode

When hasTransaction is true you should commit your transaction by calling transaction.Commit after calling SaveChanges, otherwise your changes will not be saved.


 csharp
using (var context = new MyContext())
{
    var transaction = context.Database.BeginTransaction(IsolationLevel.Serializable);
    context.Employees.Add(newEmployee);
    int updates = context.SaveChanges();
    transaction.Commit(); // solves the problem
}


Enter fullscreen mode Exit fullscreen mode

Another issue could be that there's something that reverts your changes like a database trigger. You can test that by disabling all triggers on your table


 sql
Disable Trigger All on YourTableName


Enter fullscreen mode Exit fullscreen mode

Or even on the whole database


 sql
Disable Trigger All on Database.


Enter fullscreen mode Exit fullscreen mode

You can also check this in the SQL Server Profiler but you have to make sure that the SP:StmtStarting and SP:StmtCompleted events are selected in the "Trace Properties" dialog.

Trace Dialog

Expand the "Stored Procedures" node (Yes, there's no triggers node), then check SP:StmtStarting and SP:StmtCompleted events.

Trace Events

If there are any triggers that update your table, then you will see their update statements in the SQL profiler.

Another prevalent issue (on Stackoverflow) in this category is when you are using LocalDb where the .mdf file could be overwritten every time you build your project, to fix that you need to change the CopyToOutput action to overwrite the file only if newer.


The database might not preserve your changes if your changes affect a precision level that is not supported by the data type of the database field.

A common example for this is the datetime field in MS SQL Server which has less precision than the DateTime object in .NET, check the code below.


 csharp
myEntity.Time = DateTime.Parse("2020–01–01 13:45:59.2501234"); // the last 4 digits will never be saved
dbContext.SaveChanges();


Enter fullscreen mode Exit fullscreen mode

The update statement that EF generates for the above snippet will be like this.


 sql
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [MyTable] SET [Time] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 datetime',@p1=1,@p0='2020–01–01 13:45:59.250'


Enter fullscreen mode Exit fullscreen mode

As you can see the SQL statement doesn't include the fraction part of the DateTime object. Note that this issue is one of the issues that can be spotted via the SQL Server profiler.

Top comments (1)

Collapse
 
aheisleycook profile image
privatecloudev

this is very interesting i like asp but it seems the ef has like have mamy other varialbes