0
votes

The documentation for AsNoTracking() in Entity Framework Core says that any edits to it won't be persisted when the database context is saved. I have noticed another difference when AsNoTracking() is used, namely that if the database context has unsaved edits and you query it with AsNoTracking(), those changes won't be returned. The documentation makes it sound like only edits done TO an AsNoTracking() query won't be tracked and persisted on save, but it seems that the contents returned will also be different.

If this is indeed the intended behaviour, I am unsure of the best design pattern. I have used AsNoTracking() on all my read-only queries, but this means I have a bug as my design is something like this:

Controller endpoint that modifies data:

  1. Call something in a service that may or may not alter the db
  2. Call something else in the service, that does a read-only query with AsNoTracking()
  3. Controller saves the database context

The intention is that any controller endpoint can call any number of service methods that may or may not alter the database, the database contexts are scoped so they are shared between the calls, and ultimately the controller persists the changes. The problem is that #2 in the above won't return changes done in #1. How should this be resolved? The services can call out to other services which may fetch some data from places that have already been modified, so I can't just pass the models around everywhere. Should I just remove AsNoTracking() from everywhere and call it a day? Or should I add a save call after every write? Or is there something else I could do?

TLDR: I want AsNoTracking() to be used in read-only queries for speed, but it won't return any unsaved changes. Should I remove AsNoTracking(), save after every edit, or is there a better way?

Edit: Here is a snippet of what I mean; any query with AsNoTracking() ignores any edits done to a context before it's been saved, making me wonder how AsNoTracking() could be useful at all then:

var userSessionEntry = await this.mainContext.Sessions
    .Where(t => t.AccountId == session.AccountId).FirstAsync();

userSessionEntry.AccountId = Guid.Empty;

var userSessionEntry2 = await this.mainContext.Sessions
    .Where(t => t.AccountId == session.AccountId).AsNoTracking().FirstAsync();

Console.WriteLine(userSessionEntry2.AccountId); // prints original AccountId and not an empty id

Edit 2: I'm using the latest preview version of Entity Framework Core; 5.0.0-preview.5.20278.

Thanks.

1
Please edit your question to include the source code you have which shows how you read the data with AsNoTracking() how you have unsaved changes in your context, how you try to read the data from the context and how you know the data is not correct. Please see minimal reproducible example for how to provide the code. - Progman
Hi @Progman and thanks for the reply. I am unsure of what you mean with for example "how you know the data is not correct"? Are you challenging the claim in the post? I added "proof" that using AsNoTracking() in a subsequent query, before changes are saved, does not return the modified entries to the question. I don't have a code problem so I can't provide samples, this is a design problem. - Tormigurni
Which version of EF core do you have? - Guru Stron
Hi @GuruStron! I am using the latest preview version, 5.0.0-preview.5.20278. - Tormigurni

1 Answers

0
votes

The way AsNoTracking works is that it will always bypass the DbContext's own cache (change-tracked) entities, and directly execute a query on the database. This is what is meant by the definition. The cached data can be different than the underlying database data, assuming someone else makes changes to the same entities your working with.

However, per your design, if all services in your controller use the same exact DbContext instance, then you will be fine. There are ways to do this by using scoped dependency injection of your database context to any services you have. This way all parts of your service request should use the same instance.

If you need the most up-to-date data all the time, then you'll need to use AsNoTracking for all queries you make so you always hit the database for the freshest data.

You can still make edits to entities that are no-longer change tracked, but some additional code will be required:

var managers = await DbContext.Set<Employee>()
    .AsNoTracking()
    .Where(x => x.IsManager)
    .ToListAsync();

foreach (var manager in managers)
{
    manager.Salary += 10000;

    var dbEntry = DbContext.DbEntry(manager);
    dbEntry.Property(x => x.Salary).IsModified = true;
}

await DbContext.SaveChangesAsync();

You can use the above strategy to always ensure your working with the freshest data. If you have 1000's of users actively using your service, this can actually hit your database quite a lot, so some caching strategy would be in order.