I am trying to implement an observer pattern using ninject and NHibernate.
I'd like to be able to inject observers that act as "triggers" when an object is persisted or deleted via NHibernate.
Key points-
I want the observer to be notified any time an object is persisted, including cascaded saves, which is why I am using NHibernate PostInsert/PostUpdate events.
I want to be able to inject the observers via Ninject (don't want the kernel anywhere in the nhibernate event handlers).
The observers are different depending on the type of the object being persisted, so I need a good way to know which observers should be called in the NHibernate events.
My code works great now for objects that are loaded via NHibernate using constructor injection. An observer class is injected into the domain model, which is carried through the nhibernate events and can be fired off no problem.
The problem: Our existing codebase uses default constructors for our domain objects versus a factory. Because of this, the observers will not be injected unless we switch to using a factory.
I know that switching everything to a factory will work, but I wanted to see if anyone has any better suggestions for accomplishing this. So, should I make a factory to instantiate new objects or something else?
It looks like you are making life complicated for yourself by trying to put Observer pattern on top of NHibernate's Event Handler pattern.
NHibernate already provides a way of having pluggable event listeners - why not just make use of that?
class FooPostInsertEventListener : IPostInsertEventListener
{
public void OnPostInsert(PostInsertEvent #event)
{
var entity = #event.Entity;
var entityType = entity.GetType();
if (entityType != typeof(Foo)) return;
ProcessFoo(entity);
}
}
If you are desperate to go through the Kernel, then you can even use the Kernel when configuring NHibernate. Something like this:
config.EventListeners.PostInsertEventListeners = Kernel.GetAll<IPostInsertEventListener>().ToArray();
Related
I want to use fluent-nhibernate to query my data, but when a entity gets saved, it should not be written into the database via insert/update. Instead, I want to (better: have to) serialize that object and send it to a webservice (which will map that object to a 3rd-party class that will trigger some important business-logic).
Is such behaviour possible to implement with nhibernate (call a custom method instead of update on saving)?
I would recommend creating a IRepository interface and hiding the webservice and Nhibernate functionality behind that. You could possibly use NHibernate interceptors for this, but it doesn't sound like a clean solution. Personally, I would hate to find Web service code hidden in one of Nhibernate interceptors.
We decided to use a SaveOrUpdateEventListener for this task.
I have a need to inspect the set of attached entities that would be persisted if I called Flush() on a given session. (I'm writing code that accesses a Session as part of a generic pipeline before saving and it can be used in any number of contexts.)
I find myself wishing that there were a method like
mySession.GetPersistentEntities()
so I could inspect them and perform some preprocessing.
Anyone know of a way to do this?
Thanks,
Jeff
No, NHibernate's ISession does not expose anything like that. You can either:
Track these instances yourself (not recommended)
Use standard NHibernate mechanisms:
Event listeners (e.g. IFlushEventListener, ISaveOrUpdateEventListener)
Interceptors (IInterceptor.OnFlushDirty(), OnSave())
You could "hack" a little bit into the session context:
ISession session;
var sessionContext = session.GetSessionImplementation().PersistenceContext;
foreach(var entity in sessionContext.EntitiesByKey.Values)
{
// do anything with the entity
}
However, in your case I would use flush event listeners or an interceptor.
I started messing with EF 4.0 because I am curious about the POCO possibilities... I wanted to simulate disconnected web environment and wrote the following code to simulate this:
Save a test object in the database.
Retrieve the test object
Dispose of the DataContext associated with the test object I used to retrieve it
Update the test object
Create a new data context and persist the changes on the test object that are automatically tracked within the DynamicProxy generated against my POCO object.
The problem is that when I call dataContext.SaveChanges in the Test method above, the updates are not applied. The testStore entity shows a status of "Modified" when I check its EntityStateTracker, but it is no longer modified when I view it within the new dataContext's Stores property. I would have thought that calling the Attach method on the new dataContext would also bring the object's "Modified" state over, but that appears to not be the case. Is there something I am missing? I am definitely working with self-tracking POCOs using DynamicProxies.
private static void SaveTestStore(string storeName = "TestStore")
{
using (var context = new DataContext())
{
Store newStore = context.Stores.CreateObject();
newStore.Name = storeName;
context.Stores.AddObject(newStore);
context.SaveChanges();
}
}
private static Store GetStore(string storeName = "TestStore")
{
using (var context = new DataContext())
{
return (from store in context.Stores
where store.Name == storeName
select store).SingleOrDefault();
}
}
[Test]
public void Test_Store_Update_Using_Different_DataContext()
{
SaveTestStore();
Store testStore = GetStore();
testStore.Name = "Updated";
using (var dataContext = new DataContext())
{
dataContext.Stores.Attach(testStore);
dataContext.SaveChanges(SaveOptions.DetectChangesBeforeSave);
}
Store updatedStore = GetStore("Updated");
Assert.IsNotNull(updatedStore);
}
As you stated later, you were using the POCO generator, not the self-tracking entities generator.
I've tried it as well, and became quite perplexed. It seems that the proxy classes don't quite work as expected, and there might be a bug. Then again. none of the examples on MSDN try something like this, and when they reference updates in different tiers of an app (something like we're doing here) they use self-tracking entities, not POCO proxies.
I'm not sure how these proxies work, but they do seem to store some kind of state (I managed to find the "Modified" state inside the private properties). But it seems that this property is COMPLETELY ignored. When you attach a property to a context, the context adds an entry to the ObjectStateManager, and it stores further state updates in there. At this point if you make a change - it will be registered, and applied.
The problem is that when you .Attach an entity - the Modified state from the proxy is not transferred to the state manager inside the context. Furthermore, if you use context.Refresh() the updates are override, and forgotten! Even if you pass RefreshMode.ClientWins into it. I tried setting the object state's state property to modified, but it was overridden anyway, and the original settings were restored..
It seems that there's a bug in the EF right not, and the only way to do this would be to use something like this:
using (var db = new Entities())
{
var newUser = (from u in db.Users
where u.Id == user.Id
select u).SingleOrDefault();
db.Users.ApplyCurrentValues(user);
db.SaveChanges();
}
One more thing here
Entitity Framework: Change tracking in SOA with POCO approach
It seems that POCO just doesn't support the approach you're looking for, and as I expected the self-tracking entities were created to tackle the situation you were testing, while POCO's proxies track changes only within the context they created.. Or so it seems...
Try
db.ObjectStateManager.ChangeObjectState(user, System.Data.EntityState.Modified);
Before calling SaveChanges
After playing around with the self-tracking entities, I realised what was your mistake.
Instead of trying to attach the entity to the data context, you should instead instruct that you want the data context to apply the new changes you have made to it to the database.
In this case, change the "saving" code to this:
using (var dataContext = new DataContext())
{
dataContext.Stores.ApplyChanges(testStore);
dataContext.SaveChanges();
}
At least I have tested it on my local machine, and it worked after this update :)
Hope this helps!
I think the root of your problem is your management of the Context object.
With POCO disposing the context does not notify the entities on that context that they are no longer associated with a context. The change tracking with POCO is all managed by the context so you get into some fun problems where the POCO will act like it is still attached to a context but in reality it is not and re-attaching to another context should throw an error about attaching to multiple contexts.
there is a small post about this you may want to read here:
http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/thread/5ee5db93-f8f3-44ef-8615-5002949bea71/
If you switch to self tracking I think you'll find your entities work the way you're wanting.
another option is to add a property to a partial class of your poco to track changes manually after detaching the POCO from the context you used to load it.
I want to track changes in my domain model. I know that NHibernate ISession is an inmplementation of UnitOfWork pattern, so it tracks this changes. Is there any way to pull out them, for example, before Commit() or Flush()?
Take a look at NHibernate's IInterceptor.
OnFlushDirty - will show you persisted properties on an updated object.
OnSave - will shows you persisted properties on a saved object.
You just need to create an interceptor class that implements this interface, and when you configure your NHibernate session, tell it to use that class.
Here is a fairly good article to help you get started
I think than Interceptors are a little bit obsolete. Š•rying to use NHibernate Events. I've subscribed on OnPreUpdate event. It's parameter has State and OldState properties, but OldState is allways null. Does anyone know this OldState works at all?
I'm using Unity to resolve types dynamically for a pluggable architecture. I'm also using interception to apply business rule validation via AOP (using ValidationAspects). Finally, I'm using NHibernate as an ORM to persist domain objects.
In order for AOP to work, we use the VirtualMethodInterceptor, as interface interception doesn't work with NHibernate. I have a facade over ISession that handles casting between interface and real types for repository operations.
To make sure that all objects in the graph fetched via NHibernate are properly proxied for AOP, I made an NH IInterceptor implementation and overrode the Instantiate() method, so I could supply NH with created objects rather than have it call new(). I then use Container.Resolve() to get back proxied objects with the validation injected, and give this back to NH to populate. This works OK.
The problem comes when the session flush occurs. NHibernate gets upset because the objects it sees in the graph are of the proxy type rather than the real type. The way we're mapping (all via property, all virtual) NH should be able to get all the values it needs via the proxy, if I could override the type-checking.
What I need to know is: given a transparently proxied object created by Unity with Interception enabled, is there a) any way to obtain a direct reference to the 'real' instance it's proxying, or b) to override NH and tell it to treat objects of a proxy type as if it were of a known mapped type, dynamically at runtime?
We use interception for caching. So in our class, that implements ICallHandler we have code like this:
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
//...
var nextHandler = getNext();
var realReturn = nextHandler(input, getNext);
var cacheRefreshingItemParameters = new CacheRefreshingItemParameters
{
InterfaceMethod = input.MethodBase,
InterfaceType = input.MethodBase.DeclaringType,
TargetType = input.Target.GetType() //remember original type
};
_cacheProvider.Add(cacheKey, realReturn.ReturnValue, cacheRefreshingItemParameters);
//...
return (cachedReturn);
}
We put cacheRefreshingItemParameters in cache UpdateCallback and then resolve original service:
var service = _unityContainer.Resolve(parameters.TargetType);