views:

88

answers:

4

Hi,

I've got a class named 'Entity', which can be individuals or organizations. I have a few other classes that are descendants of Entity, which have their properties defined in extension tables in my database. One of my descendants is named 'User'.

A really truncated example of my table structure:

Table: ENTITY
Id - UniqueIdentifier
LastName - Varchar(200)
FirstName - Varchar(50)

Table: USER
Id - UniqueIdentifier (FK to Entity)
Password - Varchar(20)

Using NHibernate.Linq, I fetch an instance of a non-User entity like this:

Session.Get<Entity>(myIdValue);

The resulting SQL always joins the extension tables and includes the additional columns found in the extension tables.

When I'm asking for an instance of Entity, I really don't want one of its descendants.

Can anyone point me to my mistake?

By popular demand, here's the mapping file for the Entity class hierarchy I'm referencing:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <!-- AbstractEntity -->
  <class xmlns="urn:nhibernate-mapping-2.2" name="netfile.model.filer.entities.AbstractEntity, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="Entity" abstract="true" discriminator-value="AbstractEntity">
    <id name="Id" type="System.Guid">
      <column name="Id" />
      <generator class="guid.comb" />
    </id>
    <discriminator column="Type" type="string"/>
    <property name="Xref">
      <column name="Xref" />
    </property>
    <property name="Name">
      <column name="LastName" />
    </property>
    <property name="FiledAs">
      <column name="FiledAs" />
    </property>
    <property name="RestKey">
      <column name="RestKey" />
    </property>
    <property name="SearchText">
      <column name="SearchText" />
    </property>
    <many-to-one class="netfile.model.filer.dataspaces.Dataspace, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="Dataspace">
      <column name="Dataspace_id" />
    </many-to-one>
    <many-to-one class="netfile.model.filer.Ownership, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="Ownership">
      <column name="Ownership_id" />
    </many-to-one>
    <bag name="Descriptions" cascade="all-delete-orphan" inverse="true">
      <key>
        <column name="Entity_Id" />
      </key>
      <one-to-many class="netfile.model.filer.descriptions.AbstractDescription" />
    </bag>
    <!-- All Concrete Entity Types (Org, Ind, etc) -->
    <subclass name="netfile.model.filer.entities.Entity, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="Entity">
      <property name="FirstName">
        <column name="FirstName" />
      </property>
      <property name="IsIndividual">
        <column name="IsIndividual" />
      </property>
      <property name="IsLobbyistClient">
        <column name="IsLobbyistClient" />
      </property>
      <property name="MiddleName">
        <column name="MiddleName" />
      </property>
      <property name="Prefix">
        <column name="Prefix" />
      </property>
      <property name="Suffix">
        <column name="Suffix" />
      </property>
      <property name="Occupation">
        <column name="Occupation" />
      </property>
      <bag name="ContactMethods" cascade="all" inverse="true">
        <key>
          <column name="Entity_Id" />
        </key>
        <one-to-many class="netfile.model.common.OneLineAddress, netfile.model.common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </bag>
      <many-to-one cascade="all" class="netfile.model.common.AddressUS, netfile.model.common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="BusinessAddress">
        <column name="BusinessAddress_id" />
      </many-to-one>
      <many-to-one cascade="all" class="netfile.model.common.AddressUS, netfile.model.common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="MailingAddress">
        <column name="MailingAddress_id" />
      </many-to-one>
      <many-to-one cascade="all" class="netfile.model.common.AddressUS, netfile.model.common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="DisclosureAddress">
        <column name="DisclosureAddress_id" />
      </many-to-one>
      <many-to-one class="netfile.model.filer.entities.Entity, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="Employer">
        <column name="Employer_id" />
      </many-to-one>
      <!-- Organization, Agency -->
      <!-- Agency -->
      <subclass name="netfile.model.filer.entities.Agency, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="Agency">
        <join table="Entity_Agency">
          <key>
            <column name="Entity_id" />
          </key>
          <property name="Abbreviation">
            <column name="Abbreviation" />
          </property>
          <many-to-one cascade="save-update" class="netfile.model.filer.entities.Entity, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="TechnicalSupportContact">
            <column name="TechnicalSupportContact_id" />
          </many-to-one>
        </join>
      </subclass>
      <!-- Committee -->
      <subclass name="netfile.model.filer.entities.campaign.Committee, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="Committee">
        <join table="Entity_Committee">
          <key>
            <column name="Entity_id" />
          </key>
          <property name="FilerId">
            <column name="FilerId" />
          </property>
        </join>
        <!-- GeneralPurposeCommittee -->
        <subclass name="netfile.model.filer.entities.campaign.GeneralPurposeCommittee, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="GeneralPurposeCommittee">
        </subclass>
        <!-- ControlledCommittee -->
        <subclass name="netfile.model.filer.entities.campaign.ControlledCommittee, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="ControlledCommittee">
          <bag name="ControllingOfficers" table="Entity_Links">
            <key>
              <column name="Source_Id" not-null="true"/>
            </key>
            <many-to-many class="netfile.model.filer.entities.Entity, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
              <column name="Target_Id" not-null="true"/>
            </many-to-many>
          </bag>
          <!-- PrincipalCampaignCommittee -->
          <subclass name="netfile.model.filer.entities.campaign.PrincipalCampaignCommittee, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="PrincipalCampaignCommittee">
          </subclass>
        </subclass>
      </subclass>
      <!-- User -->
      <subclass name="netfile.model.filer.entities.User, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="User">
        <join table="Entity_User">
          <key>
            <column name="Entity_id" />
          </key>
          <property name="ChallengeAnswer">
            <column name="ChallengeAnswer" />
          </property>
          <property name="ChallengeQuestion">
            <column name="ChallengeQuestion" />
          </property>
          <property name="Disabled">
            <column name="Disabled" />
          </property>
          <property name="Password">
            <column name="Password" />
          </property>
          <property name="PasswordResetKey">
            <column name="PasswordResetKey" />
          </property>
          <property name="Reference">
            <column name="Reference" />
          </property>
          <property name="SecurityLocked">
            <column name="SecurityLocked" />
          </property>
          <bag name="DataspacePermissions" cascade="all">
            <key>
              <column name="User_Id" />
            </key>
            <one-to-many class="netfile.model.filer.permissions.PermissionDataspace, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          </bag>
        </join>
        <subclass name="netfile.model.filer.entities.UserSupport, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="UserSupport">
          <join table="Entity_UserSupport">
            <key>
              <column name="Entity_id" />
            </key>
            <property name="CompanyName">
              <column name="CompanyName" />
            </property>
          </join>
        </subclass>
      </subclass>
    </subclass>
    <!-- MunicipalDecision, MunicipalDecisionSfo -->
    <subclass name="netfile.model.filer.entities.lobbyist.MunicipalDecision, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="MunicipalDecision">
      <join table="Entity_MunicipalDecision">
        <key>
          <column name="Entity_id" />
        </key>
        <property name="Description">
          <column name="Description" />
        </property>
        <property name="OutcomeSought">
          <column name="OutcomeSought" />
        </property>
      </join>
    </subclass>
    <!-- MunicipalDecisionSfo -->
    <subclass name="netfile.model.filer.entities.lobbyist.sfo.MunicipalDecisionSfo, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" discriminator-value="MunicipalDecisionSfo">
      <join table="Entity_MunicipalDecision">
        <key>
          <column name="Entity_id" />
        </key>
        <property name="Description">
          <column name="Description" />
        </property>
        <property name="OutcomeSought">
          <column name="OutcomeSought" />
        </property>
        <property name="FileNumber">
          <column name="FileNumber" />
        </property>
        <many-to-one class="netfile.model.filer.settings.ownership.AgencyDefinedLobbyingSubjectArea, netfile.data.filer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="SubjectArea">
          <column name="SubjectArea_id" />
        </many-to-one>
      </join>
    </subclass>
  </class>
</hibernate-mapping>
A: 

You must tell your config files that you want lazy-loading


Doh. So it turns out i wasn't correctly awake when i answered :)

I searched a bit about the subject and can point you towards this article by mookid (i guess he's here on SO too) thats confirms what Jamie Ides is saying which is that basically you may have a code smell here. Perhaps reconsidering the classes and their mapping could be a better idea.

samy
...which I thought was the default so maybe we need to see the table mapping for "Entity"
Tahbaza
I already have default-lazy="true" on my mapping file for this class. Should I do something more? I'll add the tests referenced in this article to see if they behave as they should.
David Montgomery
@Tahbaza I thought the same, but since a weird bug in fluent-nhibernate i try to be exhaustive for diagnosticing :) @david do you try to gain access to your lazy-loading entities. If you do, that may be the reason it's trying to load the descendants..
samy
Lazy loading has nothing to do with inheritance mapping
Paco
@Paco Yeah, indeed. I read the question incorrectly and thought that the tables described a relationship mapping, not inheritance. After a good night sleep, the mistake is quite obvious :p
samy
A: 

Without making the join, NHibernate does not know to return you a User or Entity instance. You cannot avoid this join. When you save a user, you get back a user. If you don't want to "get back a user", you can save an entity instead of a user.

Paco
+3  A: 

Your mistake is that your database is poorly designed. You are using a table-per-subclass mapping strategy but you have applied it to many apparently unrelated classes. How can an Organization have a first or last name?

When you get an Entity, NHibernate has to join all the tables in the inheritance tree because it doesn't know what concrete type to return. If you get a User or an Organization it will only join to one other table as expected.

Is Entity an abstract class? You can't get concrete objects of type Entity because the abstract class can't be constructed.

Edited to add: After seeing the mapping files, I really think you should reconsider your design. This is not a good or reasonable use of inheritance. If you really want to continue with this model, I would suggest keeping the abstract base class but not mapping it. But I think this design has much deeper problems than that.

Jamie Ide
I'm trying to implement a table-per-hierarchy mapping strategy, using joined tables to declare additional properties not found in the base class. Entity is not an abstract class; it is a concrete type; it has an abstract ancestor. Organizations and individuals are not types, they are differentiated by property values within the Entity class. So, your statement "If you get a User or an Organization it will only join to one other table as expected." is wrong - which is what prompted my initial question.
David Montgomery
Does `Session.Get<User>(myIdValue)` join to multiple tables? Please show your mapping files.
Jamie Ide
Hi Jamie, I added the existing mapping file to my original question
David Montgomery
+1  A: 

Polymorphism="explicit" should have been put into my class declaration. Polymorphism is implicit by default in NHibernate.

David Montgomery