NHibernate multi column ManyToOne mapping with Mapping By-Code - 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.
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" />

Related

NHibernate mapping by code ManyToOne with CompositeIdentity

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.

NHibernate - Could not compile the mapping document

It is my firs time using NHibernate, I'm getting source code for a program from my friend after that, the program is running well after that I'm trying to add "Stock.hbm.xml" as following:
`
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="NBooks.Core.Models"
assembly="NBooks.Core">
<class name="Stock" table="Stocks" lazy="false">
<id name="ID">
<column name="Stock_ID" />
<generator class="identity" />
</id>
<property name="Stock_name" column="Stock_name" />
<property name="Comp_ID" column="Comp_ID" />
<property name="Stock_Code" column="Stock_Code" />
<property name="Address" column="Address" />
<property name="Nots" column="Nots" />
</class>
</hibernate-mapping>
`
with my class "Stock.cs"
using System;
using System.Collections.Generic;
namespace NBooks.Core.Models
{
public class Stock : BaseModel<Stock>
{
public virtual string Stock_name { get; set; }
public virtual string Stock_Code { get; set; }
public virtual int Comp_ID { get; set; }
public virtual string Notes { get; set; }
public virtual string Address { get; set; }
public virtual bool Inactive { get; set; }
public Stock()
{
}
public Stock(string name)
{
this.Stock_name = name;
}
}
public class StockEventArgs : EventArgs
{
public Stock Stock { get; set; }
public StockEventArgs(Stock Stock)
{
this.Stock = Stock;
}
}
public delegate void StockEventHandler(Stock sender, EventArgs e);
}
the Base model is:
using System;
using System.Collections.Generic;
using NBooks.Core.Util;
using NBooks.Data.NHibernate;
using NHibernate;
namespace NBooks.Core.Models
{
public interface IBaseModel
{
int Id { get; set; }
}
public class BaseModel<T> : IBaseModel
{
IList<string> errors = new List<string>();
public virtual int Id { get; set; }
public virtual bool HasErrors {
get { return errors.Count > 0; }
}
public virtual IList<string> Errors {
get { return errors; }
}
public BaseModel()
{
}
public virtual void Validate()
{
Errors.Clear();
}
public virtual void SaveOrUpdate()
{
ITransaction trans = null;
try {
ISession session = NHibernateHelper.OpenSession();
trans = session.BeginTransaction();
session.SaveOrUpdate(this);
session.Flush();
trans.Commit();
} catch (Exception ex) {
LoggingService.Error(ex.Message);
MessageService.ShowError(ex.Message);
trans.Rollback();
}
}
public virtual void Delete()
{
ITransaction trans = null;
try {
ISession session = NHibernateHelper.OpenSession();
trans = session.BeginTransaction();
session.Delete(this);
session.Flush();
trans.Commit();
} catch (Exception ex) {
LoggingService.Error(ex.Message);
MessageService.ShowError(ex.Message);
trans.Rollback();
}
}
public static T Read(int id)
{
return NHibernateHelper.OpenSession().Load<T>(id);
}
public static IList<T> FindAll()
{
return
NHibernateHelper.OpenSession().CreateCriteria(typeof(T)).List<T>();
}
}
}
when build it appers every thing is well and no errors , when run the error "NHibernate - Could not compile the mapping document Stock.hbm.xml" appears.
Thanx in advance
I noticed you have a typo in you XML:
<property name="Nots" column="Nots" />
I would suggest that you look into using Fluent NHibernate as well. It is strongly typed (for the most part) and the mapping files are easier to read and use lambda expressions so that you don't have to go the XML route.
Your mapping would instead look like this:
public class StockMap(): ClassMap<Stock>
{
public StockMap(){
Id(x => x.Id).Column("Stock_ID").GeneratedBy.Identity();
Map(x => x.Comp_ID);
Map(x => x.Address);
Map(x => x.Notes);
}
}
You have a typo in your mapping
<property name="Nots" column="Nots" />
should be
<property name="Notes" column="Nots" />

Best way to implement Entity with translatable properties in NHibernate

Consider the following class (simplified in order to focus in the core problem):
public class Question
{
public virtual string QuestionId { get; set; }
public virtual string Text { get; set; }
public virtual string Hint { get; set; }
}
and tables:
Question
- QuestionId ((primary key, identity column and key)
- Code
QuestionTranslation
- QuestionTranslationId (primary key, identity column; not really relevant to the association)
- QuestionId (composite key element 1)
- CultureName (composite key element 2) (sample value: en-US, en-CA, es-ES)
- Text
- Hint
How I can map the Question class so the Text and Hint properties are populated using the current thread's culture. If the thread's culture is changed I would like the Text and Hint properties to automatically return the appropriate value without the need for the Question entity to be reloaded.
Note that I'm only outlining the relevant class and properties from the business side. I'm totally open to any new class or property needed to achieve the desired functionality.
An alternative to Firo's answer (yes, I copied it and adapted it and feel bad about this).
It uses a dictionary and maps the translations as composite element (so it doesn't need the id at all)
public class Question
{
public virtual string QuestionId { get; set; }
public virtual string Text
{
get
{
var translation = Translations[CultureInfo.CurrentCulture.Name];
if (translation != null) return translation.Text
return null;
}
set
{
GetTranslation(CultureInfo.CurrentCulture.Name).Text = value;
}
}
public virtual string Hint
{
get
{
var translation = Translations[CultureInfo.CurrentCulture.Name];
if (translation != null) return translation.Hint
return null;
}
set
{
GetTranslation(CultureInfo.CurrentCulture.Name).Hint = value;
}
}
private QuestionTranslation GetTranslation(CultureInfo.CurrentCulture.Name)
{
QuestionTranslation translation;
if (!Translations.TryGetValue(CultureInfo.CurrentCulture.Name, out translation))
{
translation = new QuestionTranslation()
Translations[CultureInfo.CurrentCulture.Name] = translation;
}
return translation;
}
protected virtual IDictionary<string, QuestionTranslation> Translations { get; private set; }
}
class QuestionTranslation
{
// no id, culture name
public virtual string Text { get; set; }
public virtual string Hint { get; set; }
}
mapping:
<class name="Question">
<id name="QuestionId" column="QuestionId"/>
<map name="Translations" table="QuestionTranslation" lazy="true">
<key column="QuestionId"/>
<index column="CultureName"/>
<composite-element class="QuestionTranslation">
<property name="Text"/>
<property name="Hint"/>
</composite-element>
</bag>
</class>
Edited to reflect changed answer:
public class Question
{
public virtual string QuestionId { get; set; }
public virtual string Text
{
get
{
var currentculture = CultureInfo.CurrentCulture.Name;
return Translations
.Where(trans => trans.CultureName == currentculture)
.Select(trans => trans.Text)
.FirstOrDefault();
}
set
{
var currentculture = CultureInfo.CurrentCulture.Name;
var translation = Translations
.Where(trans => trans.CultureName == currentculture)
.FirstOrDefault();
if (translation == null)
{
translation = new QuestionTranslation();
Translations.Add(translation);
}
translation.Text = value;
}
}
public virtual string Hint
{
get
{
var currentculture = CultureInfo.CurrentCulture.Name;
return Translations
.Where(trans => trans.CultureName == currentculture)
.Select(trans => trans.Hint)
.FirstOrDefault();
}
set
{
var currentculture = CultureInfo.CurrentCulture.Name;
var translation = Translations
.Where(trans => trans.CultureName == currentculture)
.FirstOrDefault();
if (translation == null)
{
translation = new QuestionTranslation();
Translations.Add(translation);
}
translation.Hint = value;
}
}
protected virtual ICollection<QuestionTranslation> Translations { get; set; }
}
class QuestionTranslation
{
public virtual int Id { get; protected set; }
public virtual string CultureName { get; set; }
public virtual string Text { get; set; }
public virtual string Hint { get; set; }
}
<class name="Question" xmlns="urn:nhibernate-mapping-2.2">
<id name="QuestionId" column="QuestionId"/>
<bag name="Translations" table="QuestionTranslation" lazy="true">
<key>
<column name="QuestionId"/>
</key>
<one-to-many class="QuestionTranslation"/>
</bag>
</class>
<class name="QuestionTranslation" table="QuestionTranslation" xmlns="urn:nhibernate-mapping-2.2">
<id name="QuestionTranslationId"/>
<many-to-one name="ParentQuestion" column="QuestionId"/>
</class>
if you have a lot of translations then change ICollection<QuestionTranslation> Translations { get; set; } to IDictionary<string, QuestionTranslation> Translations { get; set; } and map as <map> but normally the above should do it

NHibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session

I have an Class with property
[Serializable]
public class MyClass {
public MyClass ()
{
}
public virtual System.DateTime Time {
get;
set;
}
public virtual string Name {
get;
set;
}
public virtual string Department {
get;
set;
}
public virtual string Ip
{
get;
set;
}
public virtual string Address {
get;
set;
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
MyClass t = obj as MyClass ;
if (t == null)
return false;
if (this.Time == t.Time && this.Name== t.Name && this.Department== t.Department)
return true;
else
return false;
}
public override int GetHashCode()
{
int hash = 13;
hash = hash +
(null == this.Time ? 0 : this.Time.GetHashCode());
hash = hash +
(null == this.Name? 0 : this.Name.GetHashCode());
hash = hash +
(null == this.Department ? 0 : this.Department.GetHashCode());
return hash;
}
}
I am having my Nhibernate mapping as
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NhibernateTest" assembly="NhibernateTest">
<class name="MyClass" table="NhibernateTest">
<composite-id>
<key-property column="Time" type="DateTime" name="Time"></key-property>
<key-property name="Name" type="string" column="Name" ></key-property>
<key-property name="Department" type="string" column="Department" ></key-property>
</composite-id>
<property column="Ip" type="string" name="Ip" />
<property column="Address" type="string" name="Address" />
</class>
</hibernate-mapping>
I am trying to perform a bulk upload for some 40k data's with using composite key. using the following code.
public void StoreInRDBMS(List<MyClass> FileList)
{
ITransaction transaction = null;
try
{
var stopwatch = new Stopwatch();
stopwatch.Start();
ISession session = OpenSession();
using (transaction = session.BeginTransaction())
{
foreach (var File in FileList)
{
session.SaveOrUpdate(File);
}
session.Flush();
session.Clear();
transaction.Commit();
}
session.Close();
stopwatch.Stop();
var time = stopwatch.Elapsed;
}
catch (Exception ex)
{
transaction.Rollback();
}
}
but the problem is , while iterating in the loop the second record in the list throws this error
{"a different object with the same identifier value was already associated with the session: NhibernateTest.MyClass, of entity: NhibernateTest.MyClass"}
though the records are unique. and also if at all its not it should update the same.
It works file if I flush the session after every iteration in the loop like
foreach (var File in FileList)
{
session.SaveOrUpdate(File);
session.Flush();
session.Clear();
}
which should not be the case and even its talkin 17 mins for 40 k records if done by above method.
Can anybody help regarding the same.
Maybe?
foreach (var File in FileList)
{
session.SaveOrUpdate(File);
session.ExecuteUpdate();
}

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.