Reflect all changes in the ObjectContext without persisting to the database - c#

Because ObjectContext.SaveChanges() occurs within a transaction I decided it would be best for my app to first perform all queries / updates on the ObjectContext before calling SaveChanges() once to write the data to the database.
However doing this means that if I create an object and subsquently query the ObjectContext it denies it exists (presumbily because the database has yet to be updated). I assumed I could get around this with some of the SaveOptions but it doesn't look like I can.
I basically want the ObjectContext to act like a proxy that I can modify as I wish to commit all in once go. Is there a way I can achieve this?

If you don't save changes your new objects don't exist in the database and no query executed against database (= no Linq to entities query) will return them. To get objects added to context but not saved yet you must query ObjectStateManager:
var data = context.ObjectStateManager
.GetObjectStateEntries(EntityState.Added)
.Where(e => !e.IsRelationship)
.Select(e => e.Entity)
.OfType<YourEntityType>();

I've had to do something along these lines in a similar situation:
Given a list of keys and values, where some of the keys are new, and wanting to update/insert keys and values in a single SaveChanges, I first pull down all the existing keys, then maintain a separate collection of keysIncludingNewOnes, which starts off containing just the existing keys, but also gets any newly-created keys added to it.
Then when I'm looking for a key object to associate with a value, I look in keysIncludingNewOnes, rather than querying the context, since (as you have found) the context doesn't know about the new keys until a SaveChanges.
It should be relatively easy for you to remember about your 'pending addition' objects; if it's not you may have a too-long-lived context, which in general creates its own problems.

Related

How do I compare an entity in my local context with the one in the database in EF Core?

EF Core 6 and .NET 6.
Suppose all my entities have a LastUpdateAt property, which is a DateTime that gets updated every time an entity is added or modified.
I get an entity from the context and show it to the user (web page, WPF window, whatever). At some point, the user clicks a Save button.
Before I save, I want to check if the entity has been updated by someone else since I got my copy. However, I'm struggling to see how to do this.
If I query the context, it just gives me back the entity I already have (including any changes my user has made).
If I refresh the entity, it overwrites the one in my context, losing my user's changes.
How do I check if the database version has a newer time stamp than the one in my context?
Thanks
Moving the discussion here since I need to paste longer text. In this article it's said, during SaveChanges(), if the DATABASE version was modified in the mean time it will throw DbUpdateConcurrencyException. In that exception you have all 3 values and YOU can decide on how to resolve the conflict:
Resolving a concurrency conflict involves merging the pending changes from the current DbContext with the values in the database. What values get merged will vary based on the application and may be directed by user input.
There are three sets of values available to help resolve a concurrency conflict:
Current values are the values that the application was attempting to write to the database.
Original values are the values that were originally retrieved from the database, before any edits were made.
Database values are the values currently stored in the database.
If you are loading an entity, keeping a DbContext instance open, updating that entity, then saving to the same DbContext instance then by default you are relying on EF to manage concurrency. This follows a "last in wins". You can let EF manage the concurrency by adding a [ConcurrencyCheck] on the LastUpdateAt property or using a Row Version via [Timestamp]. This will cause EF to fail updating if the underlying data has been updated. From there you have to decide how you want to handle it.
If you want to perform the concurrency check yourself then there are a couple of options.
Structure your code to shorten the lifespan of the DbContext using either detached entities or projected View Models. This will generally have flow-on benefits to your code performance as the original longer-lived DbContext can easily find ways to cause bloat, or accumulate "poisoned" entities if alive too long. Automapper is a great tool to assist here where you can use ProjectTo to get the view models, then Map(source, destination) to copy the values across afterward. In this way you load the data including the last modified at value, make your changes, then when saving, you load the data, validate the modified at etc. then copy the values across and save.
Scope a DbContext instance to check the data before saving.
.
private DateTime getFooLastUpdateAt(int fooId)
{
using(var context = new AppDbContext())
{
var lastUpdateAt = context.Foos
.Where(x => x.FooId == fooId)
.Select(x => x.LastUpdateAt)
.Single();
return lastUpdateAt;
}
}
This could use an injected DbContext factory or such to create the DbContext instance..

Entity not returned from query

I have this problem where I have 2 entities connected by foreign key.
AEntity: id, idOfEntityB (foreign key, constraint), fields...
BEntity: id, fields...
I save both of them to the database with SaveChanges(), later when I try to get AEntity's idOfEntityB, I succeed but when I try to get BEntity according to the id I got from AEntity, I get nothing:
context.AEntities.Add(new AEntity {
BEntity = new BEntity { ... }
});
context.SaveChanges();
.
.
.
var id1 = context.AEntities.Select(x => x.idOfEntityB);
var bEntities = context.BEntities.Where(x => id1.Contains(x.id));
bEntities has nothing in it. but the fact I was able to have values in id1 is even more confusing since they have foreign key relations (with constraint) and furthermore, id could not be created if it was not saved to the DB.
Later, when I look in the DB I see both entities as should be.
It happens sometimes and I cant reproduce the problem, I cant give more then this as an example since there's a lot of code, I believe it has something to do with caching, and therefore would like to ask if something like that is possible or not and how.
is there a way entities are saved to the DB while the context (a different one used from the context that saved) does not hold all of them in completion?
This is likely the issue you are encountering if you are relying on seeing changes between state changes between different DbContext instances. When a DbContext has loaded entities, then another DbContext instance makes changes to those records or the records change behind the scenes in the database, that original DbContext will not refresh the entities from the database.
EF does support the ability to reload entities from the database, but when dealing with child collections it gets a bit more complicated to perform a full refresh. You effectively need to tell the DbContext to forget all of the child collections, stop tracking the parent, clear the parent's child collection, then re-attach and reload the child collection. I recently covered this in the answer for this question: Replacing a entity collection in Entity Framework Core causes DbContext to fetch the new values when not saved to db. How to reload the collection?
Ultimately a DbContext lifespan should be kept as short as possible.

SingleOrDefault and FirstOrDefault returning cached data

Some previous code I had written used the Find() method to retrieve single entities by their primary key:
return myContext.Products.Find(id)
This worked great because I had this code tucked into a generic class, and each entity had a different field name as its primary key.
But I had to replace the code because I noticed that it was returning cached data, and I need it to return data from the database each call. Microsoft's documentation confirmed this is the behavior of Find().
So I changed my code to use SingleOrDefault or FirstOrDefault. I haven't found anything in documentation that states these methods return cached data.
Now I am executing these steps:
Save an entity via EF.
Execute an UPDATE statement in SSMS to update the recently saved
record's Description field.
Retrieve the entity into a new entity variable using SingleOrDefault
or FirstOrDefault.
The entities being returned still have the old value in the Description field.
I have run a SQL trace, and verified that the data is being queried during step 3. This baffles me - if EF is making a round trip to the database, why is it returning cached data?
I've searched online, and most answers apply to the Find() method. Furthermore, they suggest some solutions that are merely workarounds (dispose the DbContext and instantiate a new one) or solutions that won't work for me (use the AsNoTracking() method).
How can I retrieve my entities from the database and bypass the EF cache?
The behaviour you're seeing is described in Microsoft's How Queries Work article under point 3:
For each item in the result set
a. If this is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance
If so, the existing entity is returned
It's described a little better in this blog post:
It turns out that Entity Framework uses the Identity Map pattern. This means that once an entity with a given key is loaded in the context’s cache, it is never loaded again for as long as that context exists. So when we hit the database a second time to get the customers, it retrieved the updated 851 record from the database, but because customer 851 was already loaded in the context, it ignored the newer record from the database (more details).
All of this is saying that if you make a query, it checks the primary key first to see if it already has it in the cache. If so, it uses what's in the cache.
How do you avoid it? The first is to make sure you're not keeping your DbContext object alive too long. DbContext objects are only designed to be used for one unit of work. Bad things happen if you keep it around too long, like excessive memory consumption.
Do you need to retrieve data to display to the user? Create a DbContext to get the data and discard that DbContext.
Do you need to update a record? Create a new DbContext, update the record and discard that DbContext.
This is why, when you use EF Core with dependency injection in ASP.NET Core, it is created with a scoped lifetime, so any DbContext object only lives for the life of one HTTP request.
In the rare case you really do need to get fresh data for a record you already have an object for, you can use EntityEntry.Reload()/EntityEntry.ReloadAsync like this:
myContext.Entry(myProduct).Reload();
That doesn't help you if you only know the ID though.
If you really really need to reload an entity that you only have the ID for, you could do something weird like this:
private Product GetProductById(int id) {
//check if it's in the cache already
var cachedEntity = myContext.ChangeTracker.Entries<Product>()
.FirstOrDefault(p => p.Entity.Id == id);
if (cachedEntity == null) {
//not in cache - get it from the database
return myContext.Products.Find(id);
} else {
//we already have it - reload it
cachedEntity.Reload();
return cachedEntity.Entity;
}
}
But again, this should only be used in limited cases, when you've already addressed any cases of long-living DbContext object because unwanted caching isn't the only consequence.
Ok, I have the same problem and finally found the answer,
You doing everything right, that's just how EF works.
You can use .AsNoTracking() for your purposes:
return myContext.Products.AsNoTracking().Find(id)
make sure you addedusing Microsoft.EntityFrameworkCore; at the top.
It works like a magic

EF Core: What's the most optimized way to "refetch" an entity in order to get access to FK objects during a creation of a new object?

I want to create a new entity and then aggregate properties on FK objects then save them to the newly created object. Think of populating a keywords field on the object to drive a google like search.
Currently, I've found that the only way to do this is to Add the new entity, call context.SaveChanges(), fetch the newly created entity with all of the include statements, and then use the fetched entity to create the keyword field.
This seems inefficient considering the number of DB hits that need to happen.
Other ORMs have the capability of refetching on save.
What is the most optimized way to handle this in EF Core?
What about this syntax?
context.Entry(blog)
.Collection(b => b.Posts)
.Load();
context.Entry(blog)
.Reference(b => b.Owner)
.Load();
You can load the references onto an existing Entity that way, if I'm understanding the process you're describing correctly.
See the docs for reference
https://learn.microsoft.com/en-us/ef/core/querying/related-data#explicit-loading

Entity Framework and DbContext - Object Tracking

I am a bit confused on the usage of DbContext in Entity Framework. Here's the scenario I'm confused about.
I use a linq query from the dbcontext to get data. Something like:
List<Transactions> transactions = DefaultContext.Transactions.ToList();
Then I update a column in one of the transactions returned in that query directly in the database.
Then I call again:
List<Transactions> transactions = DefaultContext.Transactions.ToList();
When the list returns back this time, it doesn't reflect the updates/changes I made when running the update statement, unless I loop through all my transactions and Reload them:
foreach (DbEntityEntry<Transactions> item in DefaultContext.ChangeTracker.Entries<Transactions>())
{
DefaultContext.Entry<Transactions>(item.Entity).Reload();
}
Is this normal behavior? I assume that on my initial query, they are attached to the object context. Then when I query the second time, it doesn't make a trip to the database, and just pulls out the entities from the object context, unless I clear/detach or individually reload all of the entities.
It is normal and in case of DbContext API fixed behaviour because from some very strange reason neither DbSet or DbQuery expose MergeOption property. In case of ObjectContext API you can set the behaviour by MergeOption exposed on ObjectSet and ObjectQuery. So if you want to refresh values from database (and lose your changes) you can do:
ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
ObjectSet<Transactions> set = objectContext.CreateObjectSet<Transactions>();
set.MergeOption = MergeOption.OverwriteChanges;
List<Transactions> transactions = set.ToList();
If you just want to refresh transactions but you don't want to lose your changes you can use MergeOption.PreserveChanges instead.
That depends on the MergeOption of the DefaultContext.Transactions query. The default value, AppendOnly, won't overwrite objects already in your context. You can change this to OverwriteChanges to get the behavior you're expecting.
Related to the above, this is where I landed when I had this same error. But I wanted in my case to set Merge Option to No Tracking. I ran into this when I had an excel export method that was attempting to turn off object tracking of an IQueryable. Moving through lots of data that I wasn't going to be changing, I didn't need any change tracking.
A line of code similar to the below would fail on attempting to cast some IQueryables to class ObjectQuery (but succeed on others.)
var y = ((ObjectQuery)query).MergeOption = MergeOption.NoTracking;
Instead, I replaced this with usage of AsNoTracking
query = query.AsNoTracking();
Relating back to the original question, this would be potentially like the below, Extention method on DBQuery added in System.Data.Entity
List<Transactions> transactions = DefaultContext.Transactions.AsNoTracking().ToList();
Semi-Related Article:
https://msdn.microsoft.com/en-us/library/hh949853(v=vs.113).aspx

Categories