I have a situation where I need to load part of an object graph using custom SQL (for performance reasons). So I load up this collection using my custom SQL (ISession.CreateSQLQuery()) and then I assign that list to a property on another entity object.
The problem is that when I assign the list to the property, it makes that entity dirty along with all of the objects in the list, so when I go to save the object, it does hundreds of queries to save the entire list. Is there a way that I can specify that an object is NOT dirty (after I load it up myself)?
(Yeah, I know I could turn off cascade="save-update", but I really don't want to have to do that if I can avoid it.)
I think there is a functionality to evict an entity.
That means it is not connected to NHibernate anymore.
UPDATED after Jon's various comments:
If you want NHibernate to manage the object, ie detect if it is dirty, then keep it managed.
If not, Evict() it, it won't be managed. You can still save it manually and so on, it's just that it won't be done automatically for you.
I don't see any middle ground, between automatic and manual...
Note that you can still persist in various ways, like saving manually the parent entity, a Set of child entities and so on... Many things are still possible.
Expanding on KLEs answer, I would:
Evict() the parent entity
Load the child list
Attach the list of children to the parent entity
Merge() the whole thing back into nHibernate
At that point I believe that NHibernate will recognize everything as clean.
Can you just remove the property you use to store this manually fetched data from NHibernates tracking?
Assuming that you are not persisting the property that the list is assigned to, you can remove that property from the NHibernate mapping. I haven't tested this, but my expectation is that assigning to that property would not cause IsDirty() to return true.
EDIT: Ok, try try this. Load the object from an IStatelessSession, call your custom SQL and assign the property. Then Lock the object into a new ISession and continue working with it. I think the Lock will cascade to child objects if your cascade setting is all or all-delete-orphan. If Lock does not cascade then you will have to manually walk the object graph.
I don't remember where I got this from, but I have a class of Session extensions, one of which is this:
public static Object GetOriginalEntityProperty(this ISession session, Object entity, String propertyName)
{
ISessionImplementor sessionImpl = session.GetSessionImplementation();
IPersistenceContext persistenceContext = sessionImpl.PersistenceContext;
EntityEntry oldEntry = persistenceContext.GetEntry(entity);
if ((oldEntry == null) && (entity is INHibernateProxy))
{
INHibernateProxy proxy = entity as INHibernateProxy;
Object obj = sessionImpl.PersistenceContext.Unproxy(proxy);
oldEntry = sessionImpl.PersistenceContext.GetEntry(obj);
}
if (oldEntry == null) // I'm assuming this means the object is transient and that this is the way to treat that
return false;
String className = oldEntry.EntityName;
IEntityPersister persister = sessionImpl.Factory.GetEntityPersister(className);
Object[] oldState = oldEntry.LoadedState;
Object[] currentState = persister.GetPropertyValues(entity, sessionImpl.EntityMode);
Int32[] dirtyProps = persister.FindDirty(currentState, oldState, entity, sessionImpl);
Int32 index = Array.IndexOf(persister.PropertyNames, propertyName);
Boolean isDirty = (dirtyProps != null) ? (Array.IndexOf(dirtyProps, index) != -1) : false;
return ((isDirty == true) ? oldState[index] : currentState[index]);
}
If you get the original value using this method and assign it to the persistent property it will no-longer be dirty.
You could use an interceptor and then override the FindDirty method.
Related
I have a column in all my tables called LoggedInPersonID. To avoid cluttering mapping code, an Nhibernate Interceptor overrides OnFlushDirty and OnSave to assign the LoggedInPersonID property automatically.
If LoggedInPersonID is the only property changed, I consider the entity clean. At the moment Nhibernate (rightfully) considers the entity to be dirty.
Does any mapping construct exist to escape a property from Nhibernate's dirty check, while still including the column in any inserts/updates?
Alternatively, I have considered implementing the IPreUdateEventListener interface and use the OnPreUpdate event to check whether the only difference between OldState and State is in the property LoggedInPersonID, and cancel the update if that is the case. Would that be a valid approach?
I think if you already change the property in OnSave, the dirty check will come after, and finally OnFlushDirty will occur, when it is already decided. At least if you (unnecessarily) call Save() or SaveOrUpdate() on your object, although it is not a newly created one.
Simplier case
I would rather try to avoid setting LoggedInPersonID if the entity is not dirty. I am not comfortable with cancelling the update from IPreUdateEventListener: a lot of other processing still occurs, like second level cache updating, and other PostUpdate processing.
OnFlushDirty xml doc states:
Called when an object is detected to be dirty, during a flush.
So this means NHibernate considers your object to be dirty even before you have set its LoggedInPersonID.
You should probably check that in your interceptor with a conditional break-point to stop only on your entity type having troubles, and check if there is already some other changes between currentState and previousState before your code affects its LoggedInPersonID.
Maybe have you by example some other logic elsewhere which has already set LoggedInPersonID.
Harder case
But checking NHibernate code, it could be a bit muddier. It looks to me like OnflushDirty could be called on entities which might be dirty. (And maybe this "might be dirty" is caused by what I had suspected in my answer on your previous question.)
I such case, you should do your own dirty check inside your interceptor. You may do the dirty check in your OnFlushDirty, but then NHibernate will still do its own, causing the dirty check to be done twice. To avoid dirty checking twice each entity, you need then do your first idea: evicting LoggedInPersonID from the dirty check if this is the only dirty property.
NHibernate dirty check implementation is not trivial. Better reuse it than coding your own dirty check. But this need adding some code to your interceptor. (Done with the help of this blog on NHibernate.info.)
using NHibernate;
using NHibernate.Type;
using NHibernate.Proxy;
...
public class LoggedInPersonIDInterceptor : EmptyInterceptor
{
...
// your previous code handling the setting of LoggedInPersonID
...
private ISession _session;
public override void SetSession(ISession session)
{
_session = session;
}
public override int[] FindDirty(object entity, object id,
object[] currentState, object[] previousState,
string[] propertyNames, IType[] types)
{
var sessionImpl = _session.GetSessionImplementation();
var persistenceContext = sessionImpl.PersistenceContext;
var entry = persistenceContext.GetEntry(entity);
if (entry == null)
{
// The blog post try to handle proxy case but that part looks
// buggy to me. If you do not need to handle proxies, just let
// default implementation do the job by returning null here.
return null;
}
var persister = sessionImpl.Factory.GetEntityPersister(entry.EntityName);
var dirtyPropertiesIndexes = persister.FindDirty(currentState,
previousState, entity, sessionImpl);
// Probable superfluous null check...
if (dirtyPropertiesIndexes == null || dirtyPropertiesIndexes.Length != 1)
{
return dirtyPropertiesIndexes;
}
if (propertyNames[dirtyPropertiesIndexes[0]] == "LoggedInPersonID")
{
// return empty array for telling that nothing has changed
return new int[] {};
}
return dirtyPropertiesIndexes;
}
}
Side note : I have seen in your other question revisions your were testing on propertyNames[i].ToLower() == "loggedinpersonid". If you need that, I generally prefer do that this way : StringComparer.OrdinalIgnoreCase.Equals(propertyNames[i], "LoggedInPersonID"). This avoid messing up while manually lower-casing the property name.
Other solution
Maybe this other way I found later would be easier.
I was wondering if there is actually an existing way to work out if an object is persisted yet or not? For instance an IsPersisted(object obj) method...
Checking the identifier for an empty value would work I'm sure, but I haven't fully thought this through and just wanted to be sure there wasn't something I was missing.
Thanks,
Tony
This is the entity persister's responsibility, so I'd let it figure it out instead of manually checking the unsaved-value:
public bool? IsPersisted(object obj, ISession session)
{
var sessionFactoryImpl = (ISessionFactoryImplementor)session.SessionFactory;
var persister = new SessionFactoryHelper(sessionFactoryImpl).RequireClassPersister(obj.GetType().AssemblyQualifiedName);
return !persister.IsTransient(obj, (ISessionImplementor)session);
}
The entity persister does a few more things than just checking the unsaved-value, like checking the version and the second-level cache. And it seems that it's not always possible to find out if it's transient (it returns bool?).
Checking the id against the unsaved-value is a good way. That's what the session.SaveOrUpdate method uses to decide whether to emit an INSERT or UPDATE statement.
I wrote the following ISession extension, which seems to work. If you've got something better, I'd be happy to see it.
public static bool IsNewEntity(this ISession session, object entity)
{
if (entity == null) return false;
ISessionImplementor sessionImpl = session.GetSessionImplementation();
IPersistenceContext persistenceContext = sessionImpl.PersistenceContext;
EntityEntry oldEntry = persistenceContext.GetEntry(entity);
return oldEntry == null;
}
NHibernate potentially looks at three different properties to see if an entity is persisted. In most cases checking the Id against the unsaved-value is sufficient. If the Id is assigned, a Version or Timestamp property will be checked.
Id - this works if the id is not assigned.
Version - if present and if the id is assigned.
Timestamp - if present and if the id is assigned.
I use the NHibernate OnPreInsert and OnPreUpdate events in a PreSaveEventListener to set the CreatedDate and ModifiedDate of my entities. The problem is, there are two entities for which both events get triggered when I first create them. This causes an issue because the entity state does not get saved after the OnPreInsert event, so the OnPreUpdate event operates on a whole new entity state and my CreatedDate never gets set (defaults to 01/01/0001).
At first, I thought this was because my code that was initiating two SaveOrUpdate calls back to back before the end of the transaction. Sure enough, I found some code to this effect. But then I realized this was still happening for the other entity. So far as I can tell, only these two entities have this issue. I temporarily solved the problem by setting the CreatedDate in their constructors, but I want to avoid this.
Here's my structure:
Business entity (an abstract class that has two concrete joined-subclasses)
BusinessContact entity with a Many-To-One relationship with Business
EDIT: I have recently realized that it's also happening on one other object (InvoiceLineItem), but not a near identical object (BillLineItem) instantiated and used in near identical ways. Seems rather arbitrary.
Has anyone seen this before?
Here's the event listener code:
public class PreSaveEventListener : IPreInsertEventListener, IPreUpdateEventListener {
public bool OnPreInsert(PreInsertEvent #event) {
EntityWithGuidId entity = #event.Entity as EntityWithGuidId;
if (null != entity) {
var createdDate = DateTime.Now;
var modifiedDate= DateTime.Now;
Set(#event.Persister, #event.State, "CreatedDate", createdDate);
Set(#event.Persister, #event.State, "ModifiedDate", modifiedDate);
entity.CreatedDate = createdDate;
entity.ModifiedDate = modifiedDate;
}
return false;
}
public bool OnPreUpdate(PreUpdateEvent #event) {
EntityWithGuidId entity = #event.Entity as EntityWithGuidId;
if (null != entity) {
var modifiedDate= DateTime.Now;
Set(#event.Persister, #event.State, "ModifiedDate", modifiedDate);
entity.ModifiedDate = modifiedDate;
}
return false;
}
private void Set(IEntityPersister persister, object[] state, string propertyName, object value) {
var index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index == -1)
return;
state[index] = value;
}
}
Event listeners caused a lot of different issues in my project and many of them doesn't make sense to me. I think your issue can be caused in case when NHibernate really updates your entity after it created. NHibernate can update version of entity or set some id (or guid) for it. Can you put here mapping of issued entity? I also will suggest you to look at sql queries in profiler.
I actually ran into this, there is a chance it may be the same issue.
I implemented my own StringTrimEnd typehandler that did just that, trimmed the end of strings before inserting into the database, or after retrieving them.
Well, I implemented the Equals method wrong and it returned false for Equals(object x, object y) when x and y where null.
Therefore when I created a new object with a null string on it, it compared the loaded value (null) with the current value (null) and decided an update was needed (as well as the insert).
Maybe this will help someone out at some point.
You have nullable field in DB which wasn't marked as nullable in NH
I have two related objects: ProgramSession and ProgramTask, with a one-to-many relationship. A ProgramSession has many ProgramTasks. So the objects looks like this:
public class ProgramSession
{
public virtual IList<ProgramTask> ProgramTasks
{
get { return _programTasks; }
set { _programTasks = value; }
}
}
public class ProgramTask
{
public virtual ProgramSession ProgramSession
{
get { return _programSession; }
set { _programSession = value; }
}
}
And the mappings...
ProgramSession.hbm.xml
<bag name="ProgramTasks" lazy="false" cascade="all-delete-orphan" inverse="true" >
<key column="SessionUid"></key>
<one-to-many class="ProgramTask"></one-to-many>
</bag>
ProgramTask.hbm.xml
<many-to-one name="ProgramSession" column="SessionUid" class="ProgramSession" />
The problems begin when I try to change the ProgramSession of a ProgramTask.
If I remove the ProgramTask from the ProgramSession.ProgramTasks list property of the old session, and then add it to the same property on the new session, NHibernate tells me that the a deleted object will be resaved.
If I simply change the value of the ProgramTask.ProgramSession object, I have no problem saving. However, I get weird behavior if I do not save immediately because the ProgramSession.ProgramTasks properties (on both sessions) are not synchronized until after the NHibernate session is refreshed.
Changing the ProgramTask.ProgramSession object without directly modifying the lists as well, creates an invalid state. Take the following code as an example:
programTask.ProgramSession = newProgramSession;
Assert.That(newProgramSession.ProgramTasks.Contains(programTask)); // fails
Assert.That(!oldProgramSession.ProgramTasks.Contains(programTask)); // fails
This is more problematic in code that executed later on, that assumes the ProgramTasks collections are synchronized with the ProgramSession property. For example:
foreach(var programTask in programSession.ProgramTasks)
{
// whatever
}
One hack I used to work around this was to query the list. I can't use it everywhere, and it's clearly a bad solution, but it underscores the problem:
var tasksActuallyInSession =
programSession.ProgramTasks
.Where(task => task.ProgramSession == programSession)
.ToList();
Is there any way to handle this kind of situation? A best practice? Am I doing something wrong? Is the mapping incorrect? Is there some super-secret NHibernate flag I need to set?
Not sure if I understand everything what you are doing here. Some thoughts:
If you decide to move ProgramTasks around, then they get independent and should not be mapped using cascade="all-delete-orphan". If you do this, NH removes the ProgramTask from the database when you remove it from a ProgramSession.
Map it using cascade="none" and control the lifecycle of the object yourself. (this means: store it before a ProgramSession gets stored. Delete it when it is not used anymore.)
Not sure if this this is also a problem, but note that if you have a inverse reference, you code is responsible to make the references consistent. Of course, references get cleaned up after storing to the database and loading into an empty session, this is because there is only one foreign key in the database. But this is not the way it should be done. It is not responsibility of NH to manage your references. (Its only responsibility is to persist what you are doing in memory.) So you need to make it consistent in memory, and implement your business logic as if there wasn't NHibernate behind.
Background
Similar to this question, I need to determine if an entity in my NHibernate application is dirty or not. There is a "IsDirty" method on ISession, but I want to check a specific entity, not the whole session.
This post on nhibernate.info describes a method of checking an entity by fetching its database state and comparing it to the current state of the entity.
Problem
I've copied that method, but it isn't working for me. See the code:
public static Boolean IsDirtyEntity(this ISession session, Object entity)
{
String className = NHibernateProxyHelper.GuessClass(entity).FullName;
ISessionImplementor sessionImpl = session.GetSessionImplementation();
IPersistenceContext persistenceContext = sessionImpl.PersistenceContext;
IEntityPersister persister = sessionImpl.Factory.GetEntityPersister(className);
EntityEntry oldEntry = sessionImpl.PersistenceContext.GetEntry(entity);
if ((oldEntry == null) && (entity is INHibernateProxy))
{
INHibernateProxy proxy = entity as INHibernateProxy;
Object obj = sessionImpl.PersistenceContext.Unproxy(proxy);
oldEntry = sessionImpl.PersistenceContext.GetEntry(obj);
}
Object [] oldState = oldEntry.LoadedState;
Object [] currentState = persister.GetPropertyValues(entity, sessionImpl.EntityMode);
Int32 [] dirtyProps = persister.FindDirty(currentState, oldState, entity, sessionImpl);
return (dirtyProps != null);
}
The line that that populates the currentState array by calling persister.GetPropertyValues() is the problem. The array is full of nulls, instead of the actual values from my entity.
When I stepped into the code, I found that reflection is being used to get the values from the fields -- but the fields are null. This seems like a result of the proxy, but I'm not quite sure where to go from here.
I changed my default-access strategy from "field.camelcase-underscore" to "property" and now the persister.GetPropertyValues() method returns correct values.
Too early to declare victory, but seems interesting. I was using the field access strategy because I had code in my entities' properties to track dirty state. Since I'm removing that code and going to rely on NH to determine dirty state, I was able to use the property access strategy.