Fluent NHibernate table-per-hierarchy mapping fails to discriminate child collections

Viewed 51

I'm using table-per-hierarchy to persist Cat and Dog objects the same table. They both inherit from Animal. Another object (AnimalOwner) has two collection properties - Cats and Dogs. When the AnimalOwner is loaded from the db, all animals are loaded into one of the subclass collections instead of being differentiated by discriminator.

Here are the class definitions:

public abstract class Animal
{
    public virtual int Id { get; set; }
    public virtual AnimalOwner AnimalOwner { get; set; }
}

public class Cat : Animal
{
}

public class Dog : Animal
{
}

public class AnimalOwner
{
    public virtual int Id { get; set; }
    public virtual ICollection<Dog> Dogs { get; set; } = new List<Dog>();
    public virtual ICollection<Cat> Cats { get; set; } = new List<Cat>();
}

And the mappings:

public class AnimalMap : ClassMap<Animal>
{
    public AnimalMap()
    {
        Table("Animal");
        Id(x => x.Id);
        DiscriminateSubClassesOnColumn("Type").Not.Nullable();
        References(x => x.AnimalOwner);
    }
}

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

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

public class AnimalOwnerMap : ClassMap<AnimalOwner>
{
    public AnimalOwnerMap ()
    {
        Id(x => x.Id);
        HasMany(x => x.Cats)
            .Inverse()
            .Cascade.AllDeleteOrphan();
        HasMany(x => x.Dogs)
            .Inverse()
            .Cascade.AllDeleteOrphan();
    }
}

NHibernate is not respecting the subclass types when hydrating the collections. Here is a test that demonstrates the problem:

[Test]
public void CanMapAnimalOwner()
{
    var owner = new AnimalOwner();
    owner.Cats.Add(new Cat()
    {
        AnimalOwner = owner
    });
    owner.Dogs.Add(new Dog()
    {
        AnimalOwner = owner
    });
    Session.Save(owner);
    Session.Flush();
    Session.Clear();

    var refreshedOwner = Session.Get<AnimalOwner>(owner.Id);

    Assert.AreEqual(owner.Cats.Count, refreshedOwner.Cats.Count); // Failed: Expected: 1, But Was: 2
    Assert.AreEqual(owner.Dogs.Count, refreshedOwner.Dogs.Count);
}

I can see in the generated sql that when the AnimalOwner is hydrated, the discriminator column is not used to differentiate Animal types:

SELECT cats0_.AnimalOwnerId as animalownerid3_14_1_,
       cats0_.Id            as id1_14_1_,
       cats0_.Id            as id1_14_0_,
       cats0_.AnimalOwnerId as animalownerid3_14_0_
FROM   Animal cats0_
WHERE  cats0_.AnimalOwnerId = 1 /* @p0 */

Is this a limitation of NHibernate or is my mapping setup incorrect?

1 Answers

We are using xml mapping, and in these cases we have to repeat the where condition even on collection elements:

<bag name="Cats" lazy="true" inverse="true" batch-size="50" cascade="all-delete-orphan"
     where="(Type='Cat')" >...

So, as far as I can guess from docs like these http://notherdev.blogspot.com/2012/01/mapping-by-code-set-and-bag.html in fluent we have the same option:

    HasMany(x => x.Cats)
        .Inverse()
        .Cascade.AllDeleteOrphan()
        .Where("(Type='Cat')")
        ;
Related