Hi,
I am using the NHibernate 1.2 GA. The development Env is MS Visual Studio 2005 and Database is MS SQL 2005.
First I will explain what is the object structure and what I am trying to do.
Tables and Classes
I have three tables/classes namely Member, MemberGroup and Member_MemberGroup.
Member table store the data for members.
MemberGroup table holds the groups available in system like "Admin", "Normal", "Guest". MemberGroup table is sort of Master table in which the data is one time inserted during application setup.
The Member_MemberGroup table is a link table which associates Member and MemberGroup i.e. a Member could be in multiple groups say Admin as well as Normal.
The relationship between Member and MemberGroup table is many-to-many.
What I am trying to do.
My requirement is that whenever a Member record is inserted into the database, the record(s) in the link table should also get inserted if the
data entry user has specified the MemberGroups as well.
Mapping files
Member
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="NHibernateLearning.Domain.Member, NHibernateLearning" table="Member">
<id name ="MemberId" column="MemberId" type="Int32" unsaved-value="0">
<generator class="native"></generator>
</id>
<property name="FirstName" column="FirstName"/>
<property name="LastName" column="LastName"/>
<property name="DOB" column="DOB"/>
<property name="Gender" column="Gender"/>
<property name="Address" column="Address"/>
<property name="Email" column="Email"/>
<property name="Password" column="Password"/>
<bag name="Groups" table="[PPMISA].[dbo].Member_MemberGroup" lazy="false" cascade="all">
<key column="MemberId"></key>
<many-to-many class="NHibernateLearning.Domain.MemberGroup, NHibernateLearning" column="MemberGroupId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
MemberGroup
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="NHibernateLearning.Domain.MemberGroup, NHibernateLearning" table="[PPMISA].[dbo].MemberGroup">
<id name ="MemberGroupId" column="MemberGroupId" type="Int32" unsaved-value="0">
<generator class="native"></generator>
</id>
<property name="Code" column="Code"/>
<property name="DisplayText" column="DisplayText"/>
<bag name="Members" table="[PPMISA].[dbo].Member_MemberGroup" lazy="false" cascade="all">
<key column="MemberGroupId"></key>
<many-to-many class="NHibernateLearning.Domain.Member, NHibernateLearning" column="MemberId"></many-to-many>
</bag>
</class>
</hibernate-mapping>
public class Member
{
private int m_MemberId;
private string m_FirstName;
private string m_LastName;
private DateTime m_DOB;
private char m_Gender;
private string m_Address;
private string m_Email;
private string m_Password;
private IList<MemberGroup> m_Groups = new List<MemberGroup>();
public virtual int MemberId { get { return m_MemberId; } set { m_MemberId = value; } }
public virtual string FirstName { get { return m_FirstName; } set { m_FirstName = value; } }
public virtual string LastName { get { return m_LastName; } set { m_LastName = value; } }
public virtual DateTime DOB { get { return m_DOB; } set { m_DOB = value; } }
public virtual char Gender { get { return m_Gender; } set { m_Gender = value; } }
public virtual string Address { get { return m_Address; } set { m_Address = value; } }
public virtual string Email { get { return m_Email; } set { m_Email = value; } }
public virtual string Password { get { return m_Password; } set { m_Password = value; } }
public virtual IList<MemberGroup> Groups { get { return m_Groups; } set { m_Groups = value; } }
}
public class MemberGroup
{
private int m_MemberGroupId;
private string m_Code;
private string m_DisplayText;
private IList<Member> m_Members = new List<Member>();
public virtual int MemberGroupId { get { return m_MemberGroupId; } set { m_MemberGroupId = value; } }
public virtual string Code { get { return m_Code; } set { m_Code = value; } }
public virtual string DisplayText { get { return m_DisplayText; } set { m_DisplayText = value; } }
public virtual IList<Member> Members { get { return m_Members; } set { m_Members = value; } }
}
Code Snippet where I prepare the Member class for data insert
Member newMemberData = new Member();
newMemberData.FirstName = txtFName.Text;
newMemberData.LastName = txtLName.Text;
newMemberData.DOB = Convert.ToDateTime(txtDOB.Text);
newMemberData.Address = txtAddress.Text;
newMemberData.Email = txtEmail.Text;
newMemberData.Password = txtPassword.Text;
MemberGroup normalUserGroup = new MemberGroup(); normalUserGroup.MemberGroupId = 2; //2 stands for "Normal". newMemberData.Groups.Add(normalUserGroup);
Code snippet for actual data insert operation
void IMemberManager.AddNew(Member newMemberData)
{
ISession session = SessionFactory.OpenSession();
ITransaction tx = session.BeginTransaction();
try
{
session.Save(newMemberData);
tx.Commit();
session.Close();
}
catch (Exception exp)
{
tx.Rollback();
session.Close();
throw; //Best would be throw a custom exception stating the exact reason why database insert is a failure
}
}
The Exception I am getting
[NHibernate.ADOException] = {"could not update: [NHibernateLearning.Domain.MemberGroup#2][SQL: UPDATE [PPMISA].[dbo].MemberGroup SET Code = ?, DisplayText = ? WHERE MemberGroupId = ?]"}
and the inner exception
InnerException = {"Cannot insert the value NULL into column 'Code', table 'PPMISA.dbo.MemberGroup'; column does not allow nulls. UPDATE fails.\r\nThe statement has been terminated."}
Full Stack Trace
exp.StackTrace " at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)\r\n at NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Int32[] dirtyFields, Boolean hasDirtyCollection, Object[] oldFields, Object oldVersion, Object obj, ISessionImplementor session)\r\n at NHibernate.Impl.ScheduledUpdate.Execute()\r\n at NHibernate.Impl.SessionImpl.Execute(IExecutable executable)\r\n at NHibernate.Impl.SessionImpl.ExecuteAll(IList list)\r\n at NHibernate.Impl.SessionImpl.Execute()\r\n at NHibernate.Impl.SessionImpl.Flush()\r\n at NHibernate.Transaction.AdoTransaction.Commit()\r\n at NHibernateLearning.Facade.MemberManager.NHibernateLearning.Facade.IMemberManager.AddNew(Member newMemberData) in
Query
If you closely see both the above exceptions then you can figure out that, the NHibernate is trying to update data into MemberGroup table, where as the link between Member and MemberGroup is via Member_MemberGroup link table which means whenever I insert an item into the Groups collection of Member class then a new record should be inserted into the link table not in the master table (i.e. MemberGroup table)
Is my assumption wrong?
Though I am novice to NHibernate tool but as per my understanding the Object Model in your application is mapped to database schema via mapping files and if we have indicated in mapping files that what is relationship between two Objects then NHibernate should be able to deduce the appropriate database logic.
What is surprising is that the same mappings files are working file if I read data, i.e. if I issue a Read query then NHibernate correctly returns the Member and the associated MemberGroups in Group collection, but while inserting it is trying to put data in MemberGroup whereas it should go into Link table.
Am I doing anything wrong, I mean the cascade="all" option which I have set in mapping files shouldn't be used or there is another way to manage the cascaded inserts.
Please advise.
|