EF tracking vs non-tracking - c#

When do I want to have tracking enabled and when I want it disabled in a WebAPI? It almost seems like I would always want to use this:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
with my DbContext and only when I need to persist an object I would mark the object as modified. Could you give me a specific example when I need to have tracking enabled and when I want it to be disabled?
Thanks

First Let's understand what exactly is tracking , this is a good reading about it but in short :
Tracking behavior controls if Entity Framework Core will keep
information about an entity instance in its change tracker. If an
entity is tracked, any changes detected in the entity will be
persisted to the database during SaveChanges().
var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();
as you can see in the above example if the query is tracked (which is default behavior) you don't even need to mark the object as modified , because this object is retrieved by the context it's attached to it and the context will notice changes preformed on it and persist it we SaveChanges() is called
So to answer your questions: it depends on the scenario ,if you are sure that you will not modify the retrieved data and won't need to persist any changes that you might perform on it then there is no point in using a tracked query , in fact it would benefit the performance if you used a No-Tracking query .
Think of No-Tracking queries as read-only data that you just want to retrieve to display to the user or extract some info from it
the mentioned articular talks about EF core but the tracking vs no-tracking concept are the same even in other ORMs

WebAPI will always have NoTracking.
Tracking is required when you do a fetch and then you make changes(updates) to the same object that was fetched. Now if you save that object back to DB, then tracking makes sense.
This is never the case in WebAPI.

The only context instances I use QueryTrackingBehavior.NoTracking are Reporting contexts, not API contexts unless the API applicable to that context is entirely read-only.
NoTracking will provide a nominal speed boost for data Read operations.
You can use NoTracking for update operations, but you will require a bit of additional code, and incur a nominal penalty for Updates. If you are building an Append-only (Inserts, no Updates) then NoTracking provides no penalty.
Why: When EF loads an entity with tracking, 2 things happen. First, the reference is loaded into the local cache. Second, a proxy is used which keeps track of updates against fields on the entity.
Given an update accepting a new Message for a Record entity:
void UpdateMessage(int recordId, string message);
With tracking:
void UpdateMessage(int recordId, string message)
{
using(var context = new AppContext())
{
var record = context.Records.Single(x => x.RecordId == recordId);
record.Message = message;
context.SaveChanges();
}
}
Without tracking:
void UpdateMessage(int recordId, string message)
{
using(var context = new AppContext())
{
var record = context.Records.AsNoTracking().Single(x => x.RecordId == recordId);
record.Message = message;
context.Update(record); // or Attach() and set Modified state.
context.SaveChanges();
}
}
These look very similar on the surface, but there is a distinct difference that will happen under the hood:
In the first case, EF will generate an SQL statement similar to:
UPDATE tblRecords SET Message = #1 WHERE RecordId = #0
In the second case, EF will generate:
UPDATE tblRecords SET Message = #1, SomeField = #2, SomeOtherField = #3, CreatedAt = #4, CreatedBy = #5 WHERE RecordId = #0
When taking untracked entities and "Updating" them, EF has no idea what changed, so every column is updated. With tracking, only fields that were updated will be in the query. For larger entities this can be noticeable.
Inserts, including for append-only systems aren't affected since these would include all columns anyways.

If you are projecting to viewmodels and/or sending over-the-wire, etc... tracking will not have a difference, and is a slight performance hit.
If you are doing more complicated queries where you are pulling data into memory and mutating it, then tracking makes more sense as it allows you to modify an entity and call SaveChanges again.
It's as simple as that.

Related

AsNoTracking() doesn't return unsaved changes

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:
Call something in a service that may or may not alter the db
Call something else in the service, that does a read-only query with AsNoTracking()
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.
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.

How to transition partial update operation from ObjectContext to DbContext

I am using entity framework 5.0. I am in a rocess od changing my app from ObjectContext to DbContext model. DbContext should be according to microsoft the recommended approach. I use database forst approach and I have generated model form database.
But, at a very first simple task there is a problem. Namely simple update of a record is broken.
Let's have a simple table Item, for ilustration only:
Item
(
ItemId int NOT NULL, -- Primary key
Name nvarchar(50) NOT NULL,
Description NVARCHAR(50)
)
I have noticed that using DbContext does not support updating a record not as ObjectContext does.
In my application I have a simple update method.
public void UpdateItem()
{
MyContext context = new MyContext();
Item item = new Item();
item.ItemId = 666;
context.Items.Attach(item);
// From this point onward EF tracks the changes I make to Item
Item.Description = "Some description";
context.SaveChanges();
}
Using ObjectContext this method correctly updates a record. Using SQL profiler I can see that it generates something like this (greatly simplified!!!)
UPDATE Item
SET Description = 'Some description'
WHERE ItemId = 666
If, however I try to do the same thing in DbContext I get the exception:
System.Exception: Items.aspx.cs - logged from CustomError() ---> System.Data.Entity.Validation.DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
And no database UPDATE is issued to Sql server.
I guess that DbContext validates all the properties and the property Name is null. This by design. I do not intend to modify it, I do not even know what is it and I do not need to know what is it.
Only the property Description was changed. Clearly ObjectContext does not track changes correctly.
How can this problem be resolved?
I have researched the issue and found the something on updating records.
For example this link: https://stackoverflow.com/a/15339512/4601078
db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
But this is horrible code. For every property on should add a line like:
entry.Property(e => e.Email).IsModified = true;
This produces ugly unreadable code, an I suspect lamda expression are not stelar in performance.
Even worse are those who propose to make a roundtrip to DB to fetch existing records with all properties populated, update it and the save changes. This is a no go with regard to performance.
So, how to tackle with simple entity updates or is this DbContext just another item in microsofts collection of dead ends which serve no real purpose?
DbContext doesn't really track changes by watching properties, it compares the values to previously known values. And validation always works on the entire entity so the only real choice if you want to do things this way is to disable validation during this operation. See Entity Framework validation with partial updates
If you know for sure that the changes you apply are valid, or you have custom code to validate them, you can turn off validation by EF altogether:
db.Configuration.ValidateOnSaveEnabled = false;
This works OK as long as you do it your way: attach a new entity with a known Id (aka a stub entity) and then modify its properties. EF will only update the properties it detects as having been modified (indeed by comparing original and current values, not, as ObjectContext did, by change notifications). You shouldn't mark the entity itself as modified.
If you don't want to turn off EF's validation, but neither want to mark individual properties as modified, I think this could be a useful alternative (brought to my attention by Alex's answer).

Entity Framework: can I turn off AutoDetectChangesEnabled on reading from Local?

I have a database with layers and figures on layers (for drawing). I use a SQL Server CE, create database context on application start, work with db.Layers.Local and call SaveChanges before application's exit.
All operations with this "local" db are separated into two types: read and write. When I want to read some entities I'm not going to change it.
For example:
MainModel db = new MainModel(); //created at application start and stored as field of repository
public List<Figure> GetAllFigures(){
db.Configuration.AutoDetectChangesEnabled = false; //disable before querying local
var res = db.Layers.Local.SelectMany(x=>x.Figures).ToList();
db.Configuration.AutoDetectChangesEnabled = true;
return res;
}
public void ChangeLayer(Figure figure, Layer layer){
figure.Layer = layer;
db.Figures.Local;
db.Layers.Local; //manually call detectChanges
}
So the logic is call DetectChanges on updates after any change to allow disabling it on reading. I do reading much more often than changing and reading without DetectChanges is sometimes 100 times faster. Is that logic correct and everything will work as expected? Did I understand right why EF calls DetectChanges when query to DbSet.Local?
Read everything with the AsNoTracking() extension so that the entities are not attached to the context.
As suggested, before changing the properties, attach it to the context, change the properties (layer) and mark it as modified so that it will be persisted.
Also you can call DetectChanges() so that the modifications are tracked automatically
After change Attach your entity to Context
Context.YourEntity.Attach(YourEntityObject);
// And Save here
Or use
Context.Entry(YourEntityObject).State = EntityState.Modified;

Caching Entities causes unwanted inserts

If I cache a entire table:
static List<Table1> table1Cache = context.Table1.ToList();
Then I use it to associate:
var context = new Context();
var t2 = new Table2();
t2.MyTable1Reference = table1Cache.Single(x=>x.Id == paramIntId);
context.SaveChanges();
A new row will be inserted to Table1, because of the third line. EF thinks that is a new entity. I know that I can do somethings like always Attaching the cache when create de context(I have 1 context per Request), or use MyTable1ReferenceID = table1Cache.Single(x=>x.Id == paramIntId).Id;
But its not secure, I can forget sometimes, there is a good solution?
yes, that makes sense because the entity is not currently associated with the current context. therefore EF thinks it's transient and saves a new instance.
if you are caching across contexts, then you don't want to store the object itself. that is related to the context. instead you want to store the data in cache. basically serializing and deserializing the entity. You will also need to associate the entity when the current context so the next time it's retrieved from cache you can save change to both the cache and the database.
if all this sounds like a lot, it is. keeping 2 data stores synchronized is not an easy problem to solve. I would take a look at the implementation of 2nd level cache for NHibernate.

Saving a single entity instead of the entire context

I've run into a scenario where I essentially need to write the changes of a child entity of a one-to-many association to the database, but not save any changes made to the parent entity.
The Entity Framework currently deals with database commits in the context scope (EntityContext.SaveChanges()), which makes sense for enforcing relationships, etc. But I'm wondering if there is some best practice or maybe a recommended way to go about doing fine-grained database commits on individual entites instead of the entire context.
Best practices? Do you mean, besides, "Don't do it!"?
I don't think there is a best practice for making an ObjectContext different than the state of the database.
If you must do this, I would new up a new ObjectContext and make the changes to the child entity there. That way, both contexts are consistent.
I have a similar need. The solution I am considering is to implement wrapper properties on all entities that store any property changes privately without affecting the actual entity property. I then would add a SaveChanges() method to the entity which would write the changes to the entity and then call SaveChanges() on the context.
The problem with this approach is that you need to make all your entities conform to this pattern. But, it seems to work pretty well. It does have another downside in that if you make a lot of changes to a lot of objects with a lot of data, you end up with extraneous copies in memory.
The only other solution I can think of is to, upon saving changes, save the entity states of all changed/added/deleted entities, set them to unmodified except the one you're changing, save the changes, and then restore the states of the other entities. But that sounds potentially slow.
This can be accomplished by using AcceptAllChanges().
Make your changes to the parent entity, call AcceptAllChanges(), then make your changes to the related Entities and call SaveChanges(). The changes you have made to the parent will not be saved because they have been "committed" to the Entity but not saved to the database.
using (AdventureWorksEntities adv = new AdventureWorksEntities())
{
var completeHeader = (from o in adv.SalesOrderHeader.Include("SalesOrderDetail")
where o.DueDate > System.DateTime.Now
select o).First();
completeHeader.ShipDate = System.DateTime.Now;
adv.AcceptAllChanges();
var details = completeHeader.SalesOrderDetail.Where(x => x.UnitPrice > 10.0m);
foreach (SalesOrderDetail d in details)
{
d.UnitPriceDiscount += 5.0m;
}
adv.SaveChanges();
}
This worked for me. Use the ChangeTracker.Clear() method to clear out changes for other entities.
_contextICH.ChangeTracker.Clear();
var x = _contextICH.UnitOfMeasure.Attach(parameterModel);
x.State = (parameterModel.ID != null) ? Microsoft.EntityFrameworkCore.EntityState.Modified : Microsoft.EntityFrameworkCore.EntityState.Added;
_contextICH.SaveChanges();

Categories

Resources