Table-per-subclass objects with same id collide in cache

67 views Asked by At

I need to map a legacy table using Fluent NHibernate. I have no control over the table structure.

The table looks like this:

TypeId   ObjectId   Data
10       1          ...   //Cat 1
10       2          ...   //Cat 2
20       1          ...   //Dog 1
30       1          ...

I am attempting to map this using a table-per-subclass structure using a discriminator value for TypeId.

public abstract class Animal
{
    public virtual int ObjectId { get; set; }
    public virtual string Data { get; set; }
}

public class AnimalMap : ClassMap<Animal>
{
    public AnimalMap()
    {
        Table("MyAnimals");
        Id(x => x.ObjectId);
        Map(x => x.Data);
        DiscriminateSubClassesOnColumn("TypeId")
            .AlwaysSelectWithValue();
    }
}

public class Cat : Animal
{
}

public class CatMap : SubclassMap<Cat>
{
    public CatMap()
    {
        DiscriminatorValue("10");
    }
}

public class Dog : Animal
{
}

public class DogMap : SubclassMap<Dog>
{
    public DogMap()
    {
        DiscriminatorValue("20");
    }
}

The problem occurs when I try to load Cat 1 after Dog 1, or vice versa.

var a = session.Get<Dog>(1); //Dog1
var b = session.Get<Cat>(1); //null

If I evict the first animal before fetching the second, it works.

var a = session.Get<Dog>(1); //Dog1
session.Evict(a);
var b = session.Get<Cat>(1); //Cat1

When I map both a Dog and a Cat to my AnimalHome class, I get the following exception:

Unable to cast object of type 'MyNamespace.Dog' to type 'MyNamespace.Cat'.

This leads me to believe that the cache doesn't differentiate the Dog and Cat by their types. It just thinks of it as Animal 1 and that Cat 1 equals Dog 1.

Can I somehow make the cache take the actual subclass into account? Alternatively, if this is not possible with a table-per-subclass structure, how should I approach mapping this table?

1

There are 1 answers

0
xinux On BEST ANSWER

I solved this by ditching the Discriminator. A Where mapping was more appropriate in my case, as I don't actually need to treat the Dog or Cat as an Animal in my code. I still keep code duplication to a minimum by inheriting the Animal-related mappings.

public class AnimalMap<T> : ClassMap<T>
    where T : Animal
{
    public AnimalMap()
    {
        Table("MyAnimals");
        Id(x => x.ObjectId);
        Map(x => x.Data);
    }
}

public class CatMap : AnimalMap<Cat>
{
    public CatMap()
    {
        Where("TypeId = 10");
    }
}

The cache now understands that a Cat is not a Dog.