1
votes

I have the following classes defined:

enter image description here

And these tables in my database:

enter image description here

My fluent NHibernate mappings are:

public class BusinessUnitMap : ClassMap<BusinessUnit>
{
    public BusinessUnitMap()
    {
        Table("BusinessUnits");

        Id(x => x.Id);

        Map(x => x.Code);
        Map(x => x.Name);
        Map(x => x.ParentId);
        Map(x => x.Type).Column("Type").CustomType<BusinessUnitType>();
    }
}

public class CompanyMap : SubclassMap<Company>
{
    public CompanyMap()
    {
        Table("CompanyData");

        KeyColumn("BusinessUnitID");

        Map(x => x.Something);
    }
}

public class FranchiseeMap : SubclassMap<Franchisee>
{
    public FranchiseeMap()
    {
        Table("FranchiseeData");

        KeyColumn("BusinessUnitID");

        Map(x => x.SomethingDifferent);
    }
}

public class StoreMap : SubclassMap<Store>
{
    public StoreMap()
    {
        Table("StoreData");

        KeyColumn("BusinessUnitID");

        Map(x => x.SomethingElse);
    }
}

Question #1 As far as I can tell, my code and database are setup the same as every example I've been able to find. According to those articles, NHibernate is supposed to be smart enough to determine what subclass to instantiate when I query for a particular subclass. But, when I execute the following statement:

var result = Session.QueryOver<BusinessUnit>()
                    .Where(x => x.Code == "Acme")
                    .SingleOrDefault();

an exception is thrown because it can't create an instance of the abstract BusinessUnit class. The only way I can get this to work is to specify Company as the type argument for QueryOver.

I've confirmed that using a discriminator breaks since NHibernate is looking for all of the columns to exist in a single table. Without it, though, I struggle to see how NHibernate would know what type to instantiate.

What am I doing wrong? Is the problem in my mappings, the way I'm querying, ...?

Question #2 When I change the query to something like this:

public T WithCode<T>(String code)
    where T : BusinessUnit
{
    var result = Session.QueryOver<T>()
                        .Where(x => x.Code == code)
                        .SingleOrDefault();

    return result;
}

I get an exception indicating that the UPDATE statement conflicts with a foreign key constraint. Update statement!!!! Clearly something is still not right. How can a QueryOver call result in an UPDATE statement? What am I missing?

1
NHibernate uses the existance in one of the subtables as indicator which class to instantiate. It looks like there is one acme entry which does not have a corresponding entry in one of the other tables - Firo
Not sure how you got that. "Acme" exists in both the BusinessUnits and CompanyData tables. - SonOfPirate
I reproduced the error you where describing with an entry in the base table and a missing entry in the subtable. Maybe there are other scenarios which exhibit the same error - Firo
check the update statement to see where the error might come from. Maybe you didn't post the classes which are the source of the error - Firo
I finally figured out what was going wrong with my initial implementation after changing to @filo's approach and working backwards. The ParentId property is a Guid but one of the records has null in the database. Instead of NHibernate throwing an exception because of the null Guid value (not), it thinks the property is dirty and attempts to update the record! It was only through a deep analysis of the logs did I find the statement indicating that it thought ParentId was dirty. I took a chance that it was because of the null value and make the property Guid? and, voila! Everything works. - SonOfPirate

1 Answers

1
votes

it looks like your data is not consistent. It might be better to use discrimnator mapping with optional. If you dont really need a BusinessUnitType property in code then just delete everything around the property Type

public enum BusinessUnitType
{
    Company,
    Franchisee
}

public abstract class BusinessUnit
{
    public virtual int Id { get; set; }
    public virtual string Code { get; set; }
    public virtual string Name { get; set; }
    public virtual BusinessUnit Parent { get; set; }
    public abstract BusinessUnitType Type { get; }
}

public class Company : BusinessUnit
{
    public virtual string Something { get; set; }

    public override BusinessUnitType Type { get { return BusinessUnitType.Company; } }
}

public class Franchisee : BusinessUnit
{
    public virtual string SomethingDifferent { get; set; }

    public override BusinessUnitType Type { get { return BusinessUnitType.Franchisee; } }
}

public class BusinessUnitMap : ClassMap<BusinessUnit>
{
    public BusinessUnitMap()
    {
        Table("BusinessUnits");

        Id(x => x.Id);

        Map(x => x.Code);
        Map(x => x.Name);
        References(x => x.Parent);

        DiscriminateSubClassesOnColumn("Type");

        Map(x => x.Type, "Type")
            .Access.None()
            .CustomType<BusinessUnitType>().ReadOnly();
    }
}

public class CompanyMap : SubclassMap<StrangeTablePerSubclass.Company>
{
    public CompanyMap()
    {
        DiscriminatorValue((int)new Company().Type);

        Join("CompanyData", join =>
        {
            join.KeyColumn("BusinessUnitID");
            join.Optional();
            join.Map(x => x.Something);
        });
    }
}

public class FranchiseeMap : SubclassMap<Franchisee>
{
    public FranchiseeMap()
    {
        DiscriminatorValue((int)new Franchisee().Type);
        Join("FranchiseeData", join =>
        {
            join.KeyColumn("BusinessUnitID");
            join.Optional();
            join.Map(x => x.SomethingDifferent);
        });
    }
}