I have something similar to this
var productList = order.Products.TolIst();
And I loop through the productList and update each product using
session.SaveOrUpdate(product);
But the problem is, the previous state of the product in OnFlushDirty function is null (Which make sense).
Is there anyway to manage/ copy/ inject the previous state?
Thanks
This could be related to detached objects updating. Try to read this very similar story: http://jamesfitzsimons.com/?p=152
Summary of the issue:
... On investigation we realised that the previousState parameter
passed into the onFlushDirty method of our interceptor was null. ...
The solution:
The solution was to use the merge() method (new in NHibernate 2.0).
Merge() checks the first level cache to see if an object with the
given identifier has previously been loaded. If so it loads that
object out of the first level cache and updates it’s properties using
the detached object. This means that the session is now able to track
the changes made to the object so that when the flush occurs the
previousState is no longer null.
Related
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
I am currently having problems getting my Update function working, the function first loads an entity using session.Load() and then uses session.SaveorUpdate().
My problem is that if I do not load the session first nhibernate will not know the the relationships and therefore try and insert data which is already there and when I do load the entity first, the updated entity is overwritten by the data already in the database.
public void Update(T Entity, bool load)
{
using(ISession session = this.helper.GetSession())
{
using(ITransaction transaction = session.BeginTransaction())
{
if(load)
{
session.Load(Entity, Entity.ID);
}
session.SaveOrUpdate(Entity);
transaction.Commit();
session.Flush();
}
}
}
In a nutshell:
load object and then bind it with new values (changes will be persisted on session.Flush() without any explicit Update() call) or
create new C# instance with bounded values, including ID, and call session.Update(myInstance)
The more complex answer could be found in one of the doc chapters:
9.4.2. Updating detached objects
Many applications need to retrieve an object in one transaction, send it to the UI layer for manipulation, then save the changes in a new transaction. (Applications that use this kind of approach in a high-concurrency environment usually use versioned data to ensure transaction isolation.) This approach requires a slightly different programming model to the one described in the last section. NHibernate supports this model by providing the method ISession.Update().
// in the first session
Cat cat = firstSession.Load<Cat>(catId);
Cat potentialMate = new Cat();
firstSession.Save(potentialMate);
// in a higher tier of the application
cat.Mate = potentialMate;
// later, in a new session
secondSession.Update(cat); // update cat
secondSession.Update(mate); // update mate
The usage and semantics of SaveOrUpdate() seems to be confusing for new users. Firstly, so long as you are not trying to use instances from one session in another new session, you should not need to use Update() or SaveOrUpdate(). Some whole applications will never use either of these methods.
Usually Update() or SaveOrUpdate() are used in the following scenario:
the application loads an object in the first session
the object is passed up to the UI tier
some modifications are made to the object
the object is passed back down to the business logic tier
the application persists these modifications by calling Update() in a second session
So, we can get an instance of some entity in one session... and close that session. Such object could be even totally brand new C# instance - with all its properties being bounded by some upper layer (e.g. MVC binder, or Web API formatter)
Later, we can use that instance and call session.Update(myInstance). NHibernate will take the ID of that entity and issue the proper update statement.
Another way could be to call Merge:
The last case can be avoided by using Merge(Object o). This method copies the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. The method returns the persistent instance. If the given instance is unsaved or does not exist in the database, NHibernate will save it and return it as a newly persistent instance. Otherwise, the given instance does not become associated with the session. In most applications with detached objects, you need both methods, SaveOrUpdate() and Merge().
read more in the doc
I attempted to get the object that has lazy properties in a session and tried to update it in another session. But it failed to do so
with an Error: No persister for SecUserProxy (actual class is SecUser)
I'm using NHibernate 3.4. When I googled I came to know its a bug which has been fixed.
I also came across this post in which it is said if your proxy object implements INhibernateProxy, you can unproxy the object
with NHibernate. As NHibernate no longer supports pluggable proxy factories (like Castle, LinFu etc), it uses an internal one, I'm
assuming the internal one is perhaps INhibernateProxy
So I did the following in New session where I want to update my object as:
object unprox_obj = Session
.GetSessionImplementation()
.PersistenceContext.Unproxy(secUserobj);
in anticipation of getting this same object but with the real type i-e SecUser so that it can update without any error. But it still returns a proxy object.
I'm not able to understand what is going on?
Updated:
I have realized just now that 'secUserobj' is not INhibernateProxy. So How can I make it INhibernateProxy in order to update my object within another session?
if (secUserobj is INHibernateProxy)
{
unprox_obj = Session
.GetSessionImplementation()
.PersistenceContext.Unproxy(secUserobj);
}
Detached objects (loaded in one session, and kept as reference) could be re-attached. We can use session.Merge() or session.Lock()
9.4.2. Updating detached objects
Many applications need to retrieve an object in one transaction, send it to the UI layer for manipulation, then save the changes in a new transaction....
...
...The last case can be avoided by using Merge(Object o). This method copies the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. The method returns the persistent instance. If the given instance is unsaved or does not exist in the database, NHibernate will save it and return it as a newly persistent instance. Otherwise, the given instance does not become associated with the session. In most applications with detached objects, you need both methods, SaveOrUpdate() and Merge().
19.1.4. Initializing collections and proxies
...
You may also attach a previously loaded object to a new ISession with Merge() or Lock() before accessing uninitialized collections (or other proxies). No, NHibernate does not, and certainly should not do this automatically, since it would introduce ad hoc transaction semantics!
...
So, we can pass detached reference into .Merge(), and later work with returned (brand new) object reference:
MyEntity reAttached = session.Merge<MyEntity>(detached);
Be careful, this should be done (as stated above) before any of the detached collections were touched.
I've an object which is called Uczestnik which just got saved to database
var konsultant = uczestnik.Konsultanci;
uczestnik.Konsultanci = null; // null attached object and reuse it's ID later on for SAVE purposes
uczestnik.KonsultantNazwa = konsultant.KonsultantNazwa;
uczestnik.Szkolenie = null; // null attached object and reuse it's ID later on for SAVE purposes
uczestnik.SzkolenieID = szkolenie.SzkolenieID;
context.SzkolenieUczestnicies.AddObject(uczestnik);
context.SaveChanges();
context.Detach(uczestnik); // detatch to prevent Context problems
uczestnik.Szkolenie = szkolenie;// reassign for use in ObjectListView
uczestnik.Konsultanci = konsultant; // reassign for use in ObjectListView
After it's saved it's back into ObjectListView where user decided to change some value and the value was changed (one value from multiple to be exact). If I check value's entity state it's in Unchanged state so calling .Attach and .SaveChanges() won't do anything. I can use ChangeObjectState but if there's nothing changed then there's no sense to do so.
context.SzkolenieUczestnicies.Attach(uczestnik);
//context.ObjectStateManager.ChangeObjectState(uczestnik, EntityState.Modified);
context.SaveChanges();
How can I detect the change and prevent unnecessary traffic (I can imagine situation where nothing is changed in the object that holds files 5mb big) so resaving it makes no sense. Unless Entity is smart enough to detect that only one field was changed from 15 and change only that field?
If the entity is detached from a context you can't find out what has changed unless you are reloading the original entity from the database or you are using self-tracking entities or manage a tracking somehow yourself.
If you reload the entity you can use ApplyCurrentValues:
var originalEntity = context.MyEntities.Single(e => e.Id == detachedEntity.Id);
context.MyEntities.ApplyCurrentValues(detachedEntity);
context.SaveChanges();
This method marks the properties as modified which have different values between original and detached entity. SaveChanges will create an UPDATE statement which includes only those changed properties. If no property did change, SaveChanges does nothing.
But you are not completely free from "unnecessary traffic" because you have to load the original entity, you will save an unnecessary UPDATE statement though.
If you Detach entity it is not tracked by the context. In such case you are responsible for detecting when the object has changed and inform the context about changes by using ChangeObjectState. So you must track what user has modified or implement something directly to your entities. For example implement INotifyPropertyChanged (if you are using EntityObject based entities this interface should be already implemented).
I realize there is an event ObjectMaterialized with gets called on ObjectContext after an object is materialized.
Is there a way to know when an object is currently being materialized?
An object can be re-materialized by being refreshed from the database. So I can't simply have a flag in my class indicating if I've already been materialized, because it may happen again.
Basically, when certain properties are being set, I'd like to know if they are being set as fresh values from the database (i.e. while being materialized), or if the application is calling them from elsewhere.
If i read the documentation correct then the ObjectMaterialized event fires only once when the entity object is created and then it is loaded from the database with a query or a load operation.
You can track object changes with ObjectStateManager but i don't know if it helps you find out the source of the change.
As pointed in answer by #BigL this event is not fired again when entity is refreshed. Materialization means creating an instance and that will happen only once. Refreshing only updates values in the existing instance and sets entity state.
You always know that properties are being set by refreshing because you must trigger that operation yourselves on the specified entity instance so you can control what ever flag you need to turn on or off your logic used when properties are set.