NHibernate mapping by code ManyToOne with CompositeIdentity

Wietze picture Wietze · Jun 24, 2013 · Viewed 8.1k times · Source

I am trying to convert my FluentNHibernate mappings to NHibernate Mapping By-code using NHibernate 3.3.3. The goal is to upgrade to NHibernate 3.3.3 and to cut down on the number of assemblies being distributed. I am having some trouble with translating FluentNHibernate's References mapping to a Many-To-One mapping.

Many of my entities have descriptions that need translations. For this I use a Texts table that contains these texts in all available languages. I use the text ID to reference the Texts table and then in the Data Access Object I filter the required language. This works create using NHibernate 3.1 and FluentNHibernate, with NHibernate 3.3.3 and mapping by-code however I just a MappingException saying : property mapping has wrong number of columns: Category.Description type: Text.

Where is my new mapping wrong? Or is this type of mapping not possible in NHibernate 3.3.3.

This is the Texts table (SQL-server 2008).

CREATE TABLE Texts (
    ID         int           NOT NULL,
    languageID nvarchar(10)  NOT NULL,
    Singular   nvarchar(max) NOT NULL,
    Plural     nvarchar(max) NULL,
CONSTRAINT PK_Texts PRIMARY KEY CLUSTERED (ID ASC, languageID ASC)
WITH (PAD_INDEX              = OFF,
      STATISTICS_NORECOMPUTE = OFF,
      IGNORE_DUP_KEY         = OFF,
      ALLOW_ROW_LOCKS        = ON,
      ALLOW_PAGE_LOCKS       = ON) ON [PRIMARY]) ON [PRIMARY]

The Text class:

public class Text
{
    public Text(int id, string language, string singular, string plural)
    {
        this.ID = new TextCompositeID(id, language);
        this.Singular = singular;
        this.Plural = plural;
    }
    public TextCompositeID ID { get; private set; }
    public string Plural { get; private set; }
    public string Singular { get; set; }
    public override bool Equals(object obj)
    {
        var text = (Text)obj;
        if (text == null)
        {
            return false;
        }
        return this.ID.Equals(text.ID);
    }
    public override int GetHashCode()
    {
        return this.ID.GetHashCode();
    }
}

As an example here is the Category class:

public class Category
{
    public int ID { get; set; }
    public Text Description { get; set; }
}

The FluentNHibernate xml mapping for the Category class looks like this:

<class xmlns="urn:nhibernate-mapping-2.2"
       mutable="true"
       name="Category"
       lazy="false"
       table="Category"
       where="IsObsolete=0">
    <id name="ID" type="System.Int32">
        <column name="ID" not-null="true" />
        <generator class="native" />
    </id>
    <many-to-one cascade="none"
                 class="Text"
                 name="Description">
        <column name="TextID"
                not-null="true"
                unique="false" />
    </many-to-one>
</class>

Which was generated from this:

public class CategoryMap : ClassMap<Category>
{
    public CategoryMap()
    {
        this.Table("Category");
        Not.LazyLoad();
        this.Where("IsObsolete=0");
        Id(x => x.ID)
            .Column("ID")
            .GeneratedBy.Native()
            .Not.Nullable();
        References(x => x.Description)
            .Column("DescriptionID")
            .Cascade.None()
            .Not.Unique()
            .Not.Nullable();
    }
}

This is the NHibernate ClassMapping I created:

public CategoryMap()
{
    this.Lazy(false);
    this.Mutable(true);
    this.Table("Category");
    this.Where("IsObsolete=0");
    this.Id(
        x => x.ID,
        map =>
        {
            map.Column("ID");
            map.Generator(Generators.Native);
        });
    this.ManyToOne(
        x => x.Description,
        map =>
        {
            map.Cascade(Cascade.None);
            map.Class(typeof(Text));
            map.Column("TextID");
            map.Fetch(FetchKind.Join);
            map.Lazy(LazyRelation.NoLazy);
            map.ForeignKey("none");
        });
}

From this I get this xml mapping:

<class name="Category"
       lazy="false"
       table="Category"
       where="IsObsolete=0">
    <id name="ID"
        column="ID"
        type="Int32">
        <generator class="native" />
    </id>
    <many-to-one name="Description"
                 class="Text"
                 column="TextID"
                 fetch="join"
                 foreign-key="none"
                 lazy="false" />
</class>

Answer

Wietze picture Wietze · Jun 25, 2013

I found an answer myself. I had to remove the composite ID from the Text class:

public class Text
{
    public Text(int id, string language, string singular, string plural)
    {
        this.ID = id;
        this.LanguageID = language;
        this.Singular = singular;
        this.Plural = plural;
    }
    public int ID { get; private set; }
    public string LanguageID { get; private set; }
    public string Plural { get; private set; }
    public string Singular { get; set; }
    public override bool Equals(object obj)
    {
        var text = (Text)obj;
        if (text == null)
        {
            return false;
        }
        return this.ID.Equals(text.ID);
    }
    public override int GetHashCode()
    {
        return this.ID.GetHashCode();
    }
}

The Category mapping has become:

public CategoryMap()
{
    this.Lazy(false);
    this.Mutable(true);
    this.Table("Category");
    this.Where("IsObsolete=0");
    this.Id(
        x => x.ID,
        map =>
        {
            map.Column("ID");
            map.Generator(Generators.Native);
        });
    this.ManyToOne(
        x => x.Description,
        map =>
        {
            map.Column("TextID");
            map.Fetch(FetchKind.Join);
            map.ForeignKey("none");
            map.Lazy(LazyRelation.NoLazy);
        });
}

In the Data Access Object the old QueryOver query now gets me the required result.