Intercepting NHibernate persistence operations on

2019-04-15 09:35发布

问题:

I would like to intercept persistence operations over collection properties, decide by myself if it can be synchronized with database, and call a procedure when a persistence is decided to occur over all collection at once and not by each element.

How to do that ?

These collection properties are usually mapped with one-to-many or many-to-many associations. So, when we have something like this:

myEntity.List.Add(new Item());
myEntity.List.Add(new Item());
...
session.Save(myEntity);

For a mapping having two classes (entities) and an unidirectional many-to-many association, I would like to have only two sql statements occurring: INSERT INTO, and a PROCEDURE CALL which expects to receive a list of values comma-separated that is the key values from the List collection above. The collection of keys can only be saved on this system calling a procedure with a list of values (csv).

This kind of customizing is possible to be done ?

回答1:

Well, I adopted a solution implementing NHibernate Listeners, so I have a listener like that:

public class CustomSaveUpdateEventListener : IPostInsertEventListener, IPostUpdateEventListener {

    private void ScheduleToCommit(AbstractPostDatabaseOperationEvent @event) {
        // Some filter
        if (@event.Entity.GetType().FullName.Equals(MY_ENTITY_TO_INTERCEPTS) {
            object o = @event.Entity;

            // Some logic...
            // ...
            // ...

            IQuery namedQuery = @event.Session.GetNamedSQLQuery(MY_NAMED_QUERY);
            //namedQuery.SetParameter(...); // <== You can set parameters
            //namedQuery.ExecuteUpdate(); // <== DO NOT EXCEUTE HERE, this launches more events recursively and can generate stackoverflow

            @event.Session.ActionQueue.RegisterProcess(new MyActionBeforeCommit(@event, namedQuery).SaveValuesOperation);
            }
        }
    }

    public void OnPostInsert(PostInsertEvent @event) {
        ScheduleToCommit(@event);
    }


    public void OnPostUpdate(PostUpdateEvent @event) {
        ScheduleToCommit(@event);
    }
}

And my object MyActionBeforeCommit() is scheduled to run only when and if a transaction is commited. So, in my "future action" I have:

public class MyActionBeforeCommit {
    private AbstractPostDatabaseOperationEvent e;

    private IQuery query;

    public MyActionBeforeCommit(AbstractPostDatabaseOperationEvent e, IQuery query) {
        this.e = e;
        this.query = query;
    }

    public void SaveValuesOperation() {
        // Here your query will be executed only when a transation is commited, if 
        // it's rolledback this won't be executed
        query.ExecuteUpdate(); 
    }
}

And finally we need to register the new listener on NHibernate configuration, like that:

Configuration.SetListener(ListenerType.PostInsert, new    CustomSaveUpdateEventListener());
Configuration.SetListener(ListenerType.PostUpdate, new CustomSaveUpdateEventListener());

And It works very well. This is a very nice NHibernate feature, very powerful.



回答2:

I've waited a while to respond, because I know a possible approach, but never did it myself.

It sounds like you need to write a custom NHibernate "Dialect". There is are some guidelines here and here how to do it.

You don't mention the database system you use, but your starting point would obviously be to take the the dialect for that system and find the places where you should put your customizations.

Overriding Dialects is fully supported by NHibernate. If this does not work for you, you may even have to look at a custom AbstractEntityPersister, but I don't know how to that with configuration file entries.

You can find NHibernate 3.2 sources here.

A completely different approach could be to use standard NHibernate persistence to a local database, e.g. sql-server CE, and execute a synchronization script for the "real" persistence.