I am using NHibernate interceptors to log information about Updates/Inserts/Deletes to my various entities.
Included in the information logged is the Entity Type and the Unique Id of the entity modified. The unique Id is marked as a <generator class="identity"> in the NHibernate mapping file.
The obvious problem is when logging an Insert operation using IInterceptor.OnSave() the Id of the entity has not yet been assigned.
How can I obtain the Id of the inserted entity before logging the audit information?
(I have looked into NHibernate Listeners PostSave event but can't get them working with the Spring.net configuration being used, so I would like to stick with interceptors if at all possible)
Code:
// object id parameter is null...
public override bool OnSave(object entity, object id, object[] state,
string[] propertyNames, IType[] types)
{
AddAuditItem(entity, INSERT);
return false;
}
I've worked around this problem by adding a list to my interceptor class which is populated with objects during the OnSave implementation.
In the PostFlush implementation the list is iterated over and each element is audited as an insert. The objects in this list have been persisted in PostFlush() and thus have generated IDs.
This seems to work OK but I'd be grateful if any potential pitfalls were pointed out :-)
public class AuditInterceptor : EmptyInterceptor
{
// To hold inserted items to be audited after insert has been flushed
private IList<object> insertItems = new List<object>();
public override void PostFlush(System.Collections.ICollection entities)
{
foreach (var entity in insertItems)
{
AddAuditItem(entity, INSERT);
}
insertItems.Clear();
base.PostFlush(entities);
}
public override bool OnSave(object entity, object id, object[] state,
string[] propertyNames, IType[] types)
{
var auditable = entity as IAuditable;
if (auditable != null)
insertItems.Add(entity);
return false;
}
}
try the OnFlushDirty method.. or maybe PostFlush
edit: also, can you post your code? Don't you get the Id as a parameter to OnSave?
Related
Using NHibernate v3.0. I have a class similar to this:
class Foo
{
bool barActive;
Bar bar;
}
The Bar instance is managed entirely internally to Foo:
when "barActive" is true, "bar" is set to a Bar instance.
when "barActive" is set to false, the "bar" field is set to null.
Foo.bar is mapped like so:
<many-to-one name="bar" column="BarId" cascade="all-delete-orphan" unique="true" />
However, when "bar" is set to null, it does not delete the Bar record in the database. Bar is an inherited class that is used elsewhere as well, so I can't just make this field a component.
I would have expected the "unique" constraint + "delete-orphan" to handle this. Am I missing something, or can NHibernate not handle this transparently? If it can't, it seems my only option is to raise an event so a higher-level scope can call ISession.Delete(bar).
I have a workaround, that will automatically delete the orphan. I believe it should work for NHibernate version 3 and above. It uses an interceptor - basically an object that handles various session-related events. When it detects the update operation on Foo, it will add an explicit delete for the orphaned Bar.
using System;
using System.Collections;
using NHibernate;
using NHibernate.Type;
class Interceptor : EmptyInterceptor
{
private ISession _session;
private Bar _barOrphan;
public override void SetSession(ISession session)
{
base.SetSession(session);
_session = session;
}
public override bool OnFlushDirty(object entity, object id, object[] currentStates, object[] previousStates, string[] propertyNames, IType[] types)
{
if (entity.GetType() != typeof(Foo)) return;
for (var i = 0; i < propertyNames.Length; i++)
{
if (!StringComparer.Ordinal.Equals(propertyNames[i], "bar")) continue;
object previousState = previousStates[i];
if (currentStates[i] != previousState)
{
_barOrphan = (Bar) previousState;
}
break;
}
}
public override void PostFlush(ICollection entities)
{
if (_barOrphan == null) return;
_session.Delete(_barOrphan);
_barOrphan = null;
_session.Flush();
}
}
Now, when opening your NHibernate session, you have to use one of the overloads that accepts an interceptor instance as an argument, e.g.
using (ISession session = YourSessionFactoryGoesHere.OpenSession(new Interceptor()))
{
...
}
Please note that this is just a draft, to explain the concept (I hope I did not screw up the code as I was rewriting it ;-). In a real usage scenario, you may have to deal with possible multiple orphans created in one unit of work (event on the same entity, e.g. Foo could have bar1 and bar2!), so instead of a single _barOrphan member you will need a queue of the delete actions to be executed in PostFlush(). Instead of hard-coding the types of the involved classes and the name of the property bar, you will want to use generics and a property selector (e.g. PropertySelector.GetPropertyName<Foo>(foo => foo.bar), see this link. DB constraint could be a problem, moving the delete operation to Interceptor.PreFlush() might help, but I did not test it. Don't forget about the performance impact (e.g. OnFlushDirty() is invoked for each updated entity, so don't make it a bottleneck).
I'm sending a JSON collection from Javascript through a REST web service to be Deserialized using Json.NET and then finally updated in the DB using NHibernate (I'm using Fluent).
My Json is:
{
"ID": 1,
"Name": "ObjectName",
"Keys": [
{
"ID": 6,
"Name": "ID"
}
]
}
My difficulty is that when I remove a child from the 'Key' collection in Javascript, the subsequent update only nulls the foreign key of the child - it doesn't actually delete it. I have setup what I think is the correct mappings on both the parent and child:
Object one-to-many mapping:
// one-to-many
HasMany(x => x.Keys)
.KeyColumn("ObjectID")
.Cascade.AllDeleteOrphan();
Key many-to-one mapping:
// many-to-one
References(x => x.Object)
.Cascade.None();
NHibernate code that performs the update:
using (var transaction = Session.BeginTransaction())
{
Session.SaveOrUpdate(entity);
transaction.Commit();
}
Session.Flush();
Although this example is bidirectional, I have also attempted uni-directional mappings, but so far this has had no effect; the child record persists in the database, although the association itself is broken (FK set to null).
Anything obvious that I'm missing?
Ok, the problem here lies in the fact that using the Json.NET Deserializer in a normal capacity essentially creates a new object - this object is then persisted by NHibernate. The end result is the record in the database is kept, as are all the child objects that exist in the new child object collection - however children that were removed are orphaned - since the where not removed directly from the persisted NHibernate object using .Remove or .Clear.
The solution is in two parts. Firstly, we have to use the CustomCreationConverter of Json.NET to pass in an instance of the existing object to be worked on (merge).
public static T Deserialize<T>(T existingObject, string json)
{
return JsonConvert.DeserializeObject<T>(json, new ObjectConverter<T>(existingObject));
}
public class ObjectConverter<T> : CustomCreationConverter<T>
{
public T ExistingObject { get; set; }
public ObjectConverter(T existingObject)
{
ExistingObject = existingObject;
}
public override T Create(Type objectType)
{
return ExistingObject;
}
}
This alone will not work however, as the exiting objects child collections will be added to by the children in the json collection. The remedy this, and to ensure that NHibernate knows what to do when receiving the resulting object, we need to do a bit of a Json.NET hack.
Json.Net > Serialization > JsonSerializerInternalReader.cs
private object PopulateList(IWrappedCollection wrappedList, JsonReader reader, string reference, JsonArrayContract contract)
{
// Edit // Clear the collection
wrappedList.Clear();
After re-compiling, and re-adding the DLL - it works - children that are removed in javascript are finally removed from the DB as well.
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
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.