NHibernate mapping by code ManyToOne with CompositeIdentity - nhibernate

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>

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.

Related

NHibernate multi column ManyToOne mapping with Mapping By-Code

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.
However when I compile and run I get the following exception:
NHibernate.MappingException: Multi-columns property can't be mapped through single-column API.
The XML mapping FluentNHibernate gets my looks like this:
<many-to-one cascade="none" class="TextDto" fetch="join" lazy="false" name="Name" not-found="ignore">
<column name="NameTextId" unique="false" />
<column name="LanguageId" unique="false" />
</many-to-one>
Here is my new By-Code mapping:
this.ManyToOne(u => u.Name, c =>
{
c.Cascade(Cascade.None);
c.Class(typeof(TextDto));
c.Columns(
x =>
{
x.Name("NameTextId");
x.Unique(false);
},
x =>
{
x.Name("LanguageId");
x.Unique(false);
});
c.Fetch(FetchKind.Join);
c.Lazy(LazyRelation.NoLazy);
c.NotFound(NotFoundMode.Ignore);
c.Unique(false);
});
This is the old FluentNHibernate mapping:
References(x => x.Name)
.Columns("NameTextId", "LanguageId")
.Cascade.None()
.Fetch.Join()
.NotFound.Ignore()
.Not.Unique()
.Not.LazyLoad();
For Completeness the property type involved:
public class TextDto
{
public TextCompositeId Id { get; set; }
public string PluralText { get; set; }
public string SingularText { get; set; }
public override bool Equals(object obj)
{
var text = (TextDto)obj;
if (text == null) return false;
return this.Id.Equals(text.Id);
}
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
}
And an example of the property in an entity:
public class CharacteristicValue
{
public CharacteristicValueCompositeId Id { get; set; }
public TextDto Name { get; set; }
public string LanguageIdentity { get; set; }
public string Value
{
get
{
string value = null;
if (this.ValueMultilingual != null) return this.ValueMultilingual.SingularText;
else if (!string.IsNullOrEmpty(this.ValueMeta)) return this.ValueMeta;
return value;
}
}
public TextDto ValueMultilingual { get; set; }
public string ValueMeta { get; set; }
public override bool Equals(object obj)
{
if (obj == null) return false;
if (object.ReferenceEquals(this, obj)) return true;
CharacteristicValue characteristicValue = obj as CharacteristicValue;
if (characteristicValue == null) return false;
if (this.Id != characteristicValue.Id) return false;
return true;
}
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
}
So, how do I get the xml-mapping I used to get with FluentNHibernate but with NHiberbate's Mapping By-Code?
In your mapping, remove the c.Unique(false); from the ManyToOne mapping. This setting we do apply for each column now.
this.ManyToOne(u => u.Name, c =>
{
... // the same as above
// c.Unique(false); // it is setting now related to columns
});
And you will recieve
<many-to-one name="Name" class="TextDto" fetch="join" lazy="false" not-found="ignore">
<column name="NameTextId" unique="true" />
<column name="LanguageId" />
</many-to-one>
If you will change uniqueness on one of the columns:
x =>
{
x.Name("NameTextId");
x.Unique(true); // change here
},
The unique constraint would be added to that column:
<column name="NameTextId" unique="true" />

Fluent NHibernate - mixing table-per-subclass and table-per-class-hierarchy

Give the following structure,
MyBaseClass {
public int Id {get; private set;}
}
MySubclassWithDiscriminator : MyBaseClass {
}
MySubclass : MyBaseClass {
public string SomeThing {get; set;}
}
How would I use Fluent NH to map these correctly, using a combination of table-per-subclass and table-per-class-hierarchy? I've tried a custom AutomappingConfiguration, but seem to be going around in circles:
public class AutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
return type.Namespace.Contains("Entities");
}
public override bool IsDiscriminated(Type type)
{
// only classes with additional properties should be
// using the table-per-subclass strategy
if ((type.IsAssignableFrom(typeof(MyBaseClass)) ||
type.IsSubclassOf(typeof(MyBaseClass)) &&
type.GetProperties(BindingFlags.Public |
BindingFlags.FlattenHierarchy)
.Count() <= 1))
{
return true;
}
return false;
}
}
public class SubclassConvention : ISubclassConvention
{
public void Apply(ISubclassInstance instance)
{
// Use the short name of the type, not the full name
instance.DiscriminatorValue(instance.EntityType.Name);
}
}
It seems to me from my investigation that the use of the Discriminator is a binary choice when using FNH, while an HBM has the ability to have a Discriminator column and a subclass at the same time.
EDIT - 2011-05-12
I rewrote this post to try to address James Gregory's comments.
Here's the HBM that I'm trying to achieve:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class
xmlns="urn:nhibernate-mapping-2.2"
name="Mixed_Parent"
abstract="true"
table="`Mixed_Parent`">
<id name="Id" type="System.Int32">
<generator class="identity" />
</id>
<discriminator type="String">
<column name="discriminator" />
</discriminator>
<subclass
name="Mixed_TPCH_Child"
discriminator-value="Mixed_TPCH_Child" />
<subclass
name="Mixed_TPS_Child"
discriminator-value="Mixed_TPS_Child">
<join table="`Mixed_TPS_Child`" >
<key column="Id" />
<property name="Description" type="String">
<column name="Description" />
</property>
</join>
</subclass>
</class>
</hibernate-mapping>
So, what I've seen is that the HBM that's generated is EITHER a <joined-subclass> or <subclass> without the <join> sub-element, never a combination of the two. Am I missing something here?
Here's a failing test that can be added to the SubclassPersistenceModelTests to illustrate:
namespace MixedTablePerSubclassWithTablePerClassHierarchy
{
public class Mixed_Parent
{
public virtual int Id { get; set; }
}
public class Mixed_TPCH_Child
{
}
public class Mixed_TPS_Child
{
public virtual string Description { get; set; }
}
public class Mixed_ParentMap : ClassMap<Mixed_Parent>
{
public Mixed_ParentMap()
{
Id(x => x.Id);
DiscriminateSubClassesOnColumn("discriminator");
}
}
public class Mixed_TPCH_ChildMap : SubclassMap<Mixed_TPCH_Child>
{ }
public class Mixed_TPS_ChildMap : SubclassMap<Mixed_TPS_Child>
{
public Mixed_TPS_ChildMap()
{
Map(x => x.Description);
}
}
}
[Test]
public void ShouldAllowMixedTablePerSubclassWithTablePerClassHierarchy()
{
var model = new PersistenceModel();
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_ParentMap());
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPCH_ChildMap()
);
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPS_ChildMap());
var classMapping = model.BuildMappings()
.First()
.Classes.First();
// WHAT SHOULD THIS NUMBER BE (0, 1 or 2)?
classMapping.Subclasses.Count().ShouldEqual(1);
classMapping
.Subclasses
.First()
.Type
.ShouldEqual(
typeof(
MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPS_Child)
); // WHICH OF THE CHILDREN WOULD BE FIRST?
}

NHibernate.QueryException: could not resolve property

I'm using FluentNHibernate and Linq To Nhibernate, with these entities (only the relevant parts):
public class Player : BaseEntity<Player>
{
private readonly IList<PlayerInTeam> allTeams = new List<PlayerInTeam>();
public IEnumerable<Team> Teams
{
get
{
return from playerInTeam in allTeams
where playerInTeam.Roster.Match == null
select playerInTeam.Roster.Team;
}
}
}
public class PlayerInTeam : BaseEntity<PlayerInTeam>
{
public int PlayerNumber { get; set; }
public Player Player { get; set; }
public Position Position { get; set; }
public Roster Roster { get; set; }
}
public class Roster : BaseEntity<Roster>
{
private readonly IList<PlayerInTeam> players = new List<PlayerInTeam>();
public Team Team { get; set; }
public IEnumerable<PlayerInTeam> Players { get { return players; } }
}
public class Team : BaseEntity<Team>
{
private readonly IList<Roster> allRosters = new List<Roster>();
public Team(string name, Sex sex)
{
allRosters.Add(new Roster(this));
}
public Roster DefaultRoster
{
get { return allRosters.Where(r => r.Match == null).First(); }
}
}
and the matching mappings:
public class PlayerMap : ClassMap<Player>
{
public PlayerMap()
{
HasMany<PlayerInTeam>(Reveal.Member<Player>("allTeams"))
.Inverse()
.Cascade.AllDeleteOrphan()
.Access.CamelCaseField();
}
}
public class PlayerInTeamMap : ClassMap<PlayerInTeam>
{
public PlayerInTeamMap()
{
References(pit => pit.Player)
.Not.Nullable();
References(pit => pit.Roster)
.Not.Nullable();
}
}
public class RosterMap : ClassMap<Roster>
{
public RosterMap()
{
References(tr => tr.Team)
.Not.Nullable();
HasMany<PlayerInTeam>(Reveal.Member<Roster>("players"))
.Inverse()
.Cascade.AllDeleteOrphan()
.Access.CamelCaseField();
}
}
public class TeamMap : ClassMap<Team>
{
public TeamMap()
{
HasMany<Roster>(Reveal.Member<Team>("allRosters"))
.Inverse()
.Cascade.AllDeleteOrphan()
.Access.CamelCaseField();
}
}
I have this repository method:
public IEnumerable<Player> PlayersNotInTeam(Team team)
{
return from player in Session.Linq<Player>()
where !player.Teams.Contains(team)
select player;
}
Which gives me this exception: NHibernate.QueryException: could not resolve property: Teams of: Emidee.CommonEntities.Player [.Where(NHibernate.Linq.NhQueryable`1[Emidee.CommonEntities.Player], Quote((player, ) => (Not(.Contains(player.Teams, p1, )))), )]
I've looked inside the hbm file, generated by Fluent NHibernate, concerning Player, and here is what I get:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Emidee.CommonEntities.Player, Emidee.CommonEntities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=65c7ad487c784bec" table="Players">
<id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Id" />
<generator class="increment" />
</id>
<bag access="field.camelcase" cascade="all-delete-orphan" inverse="true" name="allTeams" mutable="true">
<key>
<column name="Player_id" not-null="true" />
</key>
<one-to-many class="Emidee.CommonEntities.PlayerInTeam, Emidee.CommonEntities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=65c7ad487c784bec" />
</bag>
</class>
</hibernate-mapping>
Indeed, I don't have a Teams property for Player in that file.
But shouldn't my mapping take care of this?
Where do you think the problem is?
Thanks in advance
Mike
Technically, it's not possible to look inside a method body in C-sharp. So FluentNhibernate can not do that as well. You have to apply a different strategy to model your entities and/or generate the mapping yourself.
The hbm.xml file is your mapping. So, as long as you do not inform NHibernate that your Player class has a Teams property, NHibernate doesn't know anything about it.
Next to that, why do you have an hbm.xml mapping file, and a mapping using Fluent ?
(It seems that the hbm.xml file has priority/precedes the Fluent NH mapping).

NHibernate.Linq System.Nullable throws ArgumentException, the value "" is not type

I have a class of type MetadataRecord:
public class MetadataRecord {
public virtual long? IntegerObject { get; set; }
public virtual string ClassName { get; set; }
public virtual DateTime? DateObject { get; set; }
public virtual double? DecimalObject { get; set; }
public virtual long MetadataId { get; set; }
public virtual long MetadataLabelId { get; set; }
public virtual long ObjectId { get; set; }
public virtual string StringObject { get; set; }
public virtual Asset Asset { get; set; }
}
and a matching mapping file as follows:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="ActiveMediaDataAccess"
namespace="ActiveMediaDataAccess.Entities">
<class name="MetadataRecord" table="WM_META_DATA" lazy="true">
<id name="MetadataId" column="META_DATA_ID">
<generator class="seqhilo" />
</id>
<property name="MetadataLabelId" column="META_DATA_LABEL_ID" />
<property name="ObjectId" column="OBJECT_ID" />
<property name="ClassName" column="CLASS_NAME" />
<property name="IntegerObject" column="INTEGER_OBJECT" />
<property name="DecimalObject" column="DECIMAL_OBJECT" />
<property name="DateObject" column="DATE_OBJECT" />
<property name="StringObject" column="STRING_OBJECT" />
<many-to-one name="Asset" column="OBJECT_ID" not-null="true" />
</class>
</hibernate-mapping>
I'm running a unit test against this class to check for values returned for IntegerObject which is a nullable type of long, from an instance of MetadataRecord. I'm using NHibernate.Linq (v 1.1.0.1001) to query as follows:
[TestMethod()]
public void IntegerObjectTest() {
var integerObject = _sessionFactory.OpenSession().Linq<MetadataRecord>()
.Where(m => m.ObjectId == 65675L)
.Select(m => m.IntegerObject)
.FirstOrDefault();
Assert.IsNull(integerObject);
}
The INTEGER_OBJECT column from the corresponding table is nullable, and I expect IsNull to be true or false. However, I get the following error:
Test method ActiveMediaMetadataViewerTestProject.MetadataRecordTest.IntegerObjectTest threw exception: NHibernate.Exceptions.GenericADOException: Unable to perform find[SQL: SQL not available] ---> System.ArgumentException: The value "" is not of type "System.Nullable`1[System.Int64]" and cannot be used in this generic collection.
Parameter name: value.
I can't figure out why it's trying to cast a string to a nullable type. Is there another way in which I should be opening the session, decorating the class, even constructing the mapping file, ..... where am I going wrong here? I could resort to using Criteria, but I was much enjoying the intellisense and "refactorability" with Linq.
Better solution (translated to SQL in whole):
[TestMethod()]
public void IntegerObjectTest() {
var integerObject = _sessionFactory.OpenSession().Linq<MetadataRecord>()
.Where(m => m.ObjectId == 65675L)
.Select(m => new long?(m.IntegerObject))
.FirstOrDefault();
Assert.IsNull(integerObject);
}
My workaround:
[TestMethod()]
public void IntegerObjectTest() {
var integerObject = _sessionFactory.OpenSession().Linq<MetadataRecord>()
.Where(m => m.ObjectId == 65675L)
.Select(m => m.IntegerObject)
.AsEnumerable()
.FirstOrDefault();
Assert.IsNull(integerObject);
}
For some reason, NHibernate.Linq does not like calling First(), FirstOrDefault() (and I'm guessing Single() and SingleOrDefault()) on nullable types, and throws the above error if the field is null. It works fine if the nullable type actually has a value. If I push the results into an in-memory collection via AsEnumerable(), ToArray(), ToList(), etc, then it plays nice and returns my nullable type.

Querying Overriding Entities Using a Self Join and the NHibernate Criteria API

I have a simple Waiver model, and I would like to make a query that returns all the Waivers that are not overridden.
public class Waiver
{
private readonly int id;
protected Waiver()
{
this.id = 0;
}
public virtual int Id { get { return id; } }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual bool IsRequired { get; set; }
public virtual DateTime EffectiveDate { get; set; }
public virtual Waiver OverriddenWaiver { get; set; }
}
Here's the map:
<class name="Waiver" table="Music_Waivers">
<id name="id" access="field" column="WaiverId" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" column="Name" />
<property name="Description" column="Description" />
<property name="IsRequired" column="IsRequired" />
<property name="EffectiveDate" column="EffectiveDate" />
<many-to-one name="OverriddenWaiver" class="Waiver" column="OverrideWaiverId" />
</class>
Now I want to have a method in my Repository with the signature public IList GetLatest(). For some reason I'm having a hard time implementing this with the CriteriaAPI. I can write this in T-SQL no problem.
I ended up brute forcing a solution. It's not pretty, but since I know the table will be tiny (probably going to end up being only 5 rows) I came up with the following code solution:
public IList<Waiver> GetLatest()
{
using (var session = SessionManager.OpenSession())
{
var criteria = session.CreateCriteria(typeof (Waiver));
var waivers = criteria.List<Waiver>();
var nonOverridenWaivers = new List<Waiver>();
foreach(var waiver in waivers)
{
bool overrideExists = waivers.Any(w => w.Overrides != null &&
w.Overrides.Id == waiver.Id);
if (!overrideExists)
nonOverridenWaivers.Add(waiver);
}
return nonOverridenWaivers;
}
}