views:

665

answers:

5

I have a problem with uninitialized proxies in nhibernate

The Domain Model

Let's say I have two parallel class hierarchies: Animal, Dog, Cat and AnimalOwner, DogOwner, CatOwner where Dog and Cat both inherit from Animal and DogOwner and CatOwner both inherit from AnimalOwner. AnimalOwner has a reference of type Animal called OwnedAnimal.

Here are the classes in the example:

public abstract class Animal
{
   // some properties
}

public class Dog : Animal
{
   // some more properties
}

public class Cat : Animal
{
   // some more properties
}

public class AnimalOwner 
{
   public virtual Animal OwnedAnimal {get;set;}
   // more properties...
}

public class DogOwner : AnimalOwner
{
   // even more properties
}

public class CatOwner : AnimalOwner
{
   // even more properties
}

The classes have proper nhibernate mapping, all properties are persistent and everything that can be lazy loaded is lazy loaded.

The application business logic only let you to set a Dog in a DogOwner and a Cat in a CatOwner.

The Problem

I have code like this:

public void ProcessDogOwner(DogOwner owner)
{
   Dog dog = (Dog)owner.OwnedAnimal;
   ....
}

This method can be called by many diffrent methods, in most cases the dog is already in memory and everything is ok, but rarely the dog isn't already in memory - in this case I get an nhibernate "uninitialized proxy" but the cast throws an exception because nhibernate genrates a proxy for Animal and not for Dog.

I understand that this is how nhibernate works, but I need to know the type without loading the object - or, more correctly I need the uninitialized proxy to be a proxy of Cat or Dog and not a proxy of Animal.

Constraints

  • I can't change the domain model, the model is handed to me by another department, I tried to get them to change the model and failed.
  • The actual model is much more complicated then the example and the classes have many references between them, using eager loading or adding joins to the queries is out of the question for performance reasons.
  • I have full control of the source code, the hbm mapping and the database schema and I can change them any way I want (as long as I don't change the relationships between the model classes).
  • I have many methods like the one in the example and I don't want to modify all of them.

Thanks,
Nir

A: 

I can't use any solution that requires adding joins to the query.

Why?

Justice
A: 

You might want to try this to see the proxied type (assuming NH 2.0+):

((INHibernateProxy)proxy).HibernateLazyInitializer.PersistentClass

But this kind of casting or "type peeking" is very bad practice anyway...

Mauricio Scheffer
+3  A: 

It's easiest to turn off lazy loading for the animal class. You say it's mostly in memory anyway.

<class name="Animal" lazy="false">
<!-- ... -->
</class>

OR

You can use generics on the animal owner to make the reference a concrete class.

class AnimalOwner<TAnimal>
{
  virtual TAnimal OwnedAnimal {get;set;}
}

class CatOwner : AnimalOwner<Cat>
{
}

class DogOwner : AnimalOwner<Dog>
{
}

OR

You can map the DogOwners and CatOwners in separate tables, and define the concrete animal type in the mapping.

<class name="CatOwner">
  <!-- ... -->
  <property name="OwnedAninal" class="Cat"/>
</class>
<class name="DogOwner">
  <!-- ... -->
  <property name="OwnedAninal" class="Dog"/>
</class>
Stefan Steinegger
Thanks, I'm not sure I can use those techniques in my case but I going to check them out.
Nir
+4  A: 

I think we recently had a similar problem, AFAIR solution was to give 'Animal' a self -"method/property":

public Animal Self { get { return this; } }

This could then be casted to correct "animal". What happens is that your original object has a reference to nhibernate proxy object (when it's lazily loaded), which acts as Animal for all methods exposed via Animal class (it passes all calls to the loaded object). However it cannot be casted as any of your other animals because it is none of these, it only emulates the Animal class. However the class that is encapsulated by AnimalProxy can be casted as subclassed animal because it is a real instance of correct class, you only need to get to it's this reference.

Pasi Savolainen
A: 

If we've been working with the same problem the issue is that the proxy generated is the proxy of Animal rather than of Dog.

The solution we used was to reload the object:

Dog dog = this.CurrentSession.Load<Dog>(owner.OwnedAnimal.AnimalID);

This goes back to your session and reloads the object with the correct type.

Hope this helps

Liath