Issue when quering Hierarchy to Hierarchy relationship - nhibernate

A Teacher has a one-to-one with a Student.
A SpecialTeacher extends Teacher but deals specifically with SpecialStudents.
Using table per class in the hierarchies.
public class Teacher
{
public virtual int Id { get; set; }
public virtual int DepartmentId { get; set; }
public virtual String Name { get; set; }
public virtual Student Student { get; set; }
}
public class SpecialTeacher : Teacher
{
public virtual string TelephoneNumber { get; set; } //SpecialTeachers get to have a phone
public virtual SpecialStudent SpecialStudent { get { return (SpecialStudent)base.Student; } set { Student = value; } }
}
public class Student
{
public virtual int Id { get; set; }
public String Name { get; set; }
}
public class SpecialStudent : Student
{
public int SpecialMark { get; set; }
}
and the associated mappings:
<class name="Student">
<id name="Id" />
<property name="Name" />
</class>
<joined-subclass name="SpecialStudent" extends="Student">
<key column="Id" />
<property name="SpecialMark" />
</joined-subclass>
<class name="Teacher">
<id name="Id" />
<property name="DepartmentId" />
<property name="Name" />
<many-to-one name="Student" column="StudentId" />
</class>
<joined-subclass name="SpecialTeacher" extends="Teacher">
<key column="Id" />
<property name="TelephoneNumber" />
</joined-subclass>
So, let's say that we want to get the average mark for SpecialStudents for a given department:
public double GetAverageScoreForSpecialStudentsByDepartment(int departmentId)
{
return CurrentSession.Query<SpecialTeacher>()
.Where(st => st.DepartmentId == departmentId)
.Average(ss => ss.SpecialStudent.SpecialMark);
}
The test will fail because it will complain that SpecialStudent is not a mapped property of SpecialTeacher.
The only way that I can think of avoiding this issue is to map the property, but this is duplication since the base Teacher is already mapped to the Student hierarchy.
Update
I meant to also mention that previously we had the SpecialTeacher set up like:
public class SpecialTeacher : Teacher
{
public virtual string TelephoneNumber { get; set; } //SpecialTeachers get to have a phone
public virtual new SpecialStudent Student { get { return (SpecialStudent)base.Student; } set { Student = value; } }
}
which did appear to work ok, but Envers did not work with it when retrieving audited data.

The only way that I can think of avoiding this issue is to map the property, but this is duplication since the base Teacher is already mapped to the Student hierarchy.
This is not duplication as you never mapped the SpecialStudent property in the SpecialTeacher mapping file. Although you correctly defined the relationship in code, NHibernate has no way of knowing a SpecialTeacher is suppose to have a SpecialStudent. The code is use by NHibernate to recreate the object from the tables, but only if you define the correct relationships in your mapping.
Remeber that BaseTeacher to BaseStudent does not imply SpecialTeacher to SpecialStudent relationship.

Related

Map Entities to self-joined Table by Id

My legacy table "AllData" has those columns:Id, Title, LookupColumn1Id
My entities:
public class BaseEntity
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Employee: BaseEntity
{
public virtual int DepartmentId { get; set; }
public virtual string DepartmentName { get; set; }
}
public class Department: BaseEntity
{
public virtual int HeadManagerId { get; set; }
}
I want to generate SELECT like this:
SELECT EmployeeTable.Title, DepartmentTable.Id, DepartmentTable.Title
FROM AllData EmployeeTable left outer join AllData DepartmentTable on EmployeeTable.LookupColumn1Id=DepartmentTable.Id
WHERE EmployeeTable.tp_ListId = #p0 and (DepartmentTable.Title = #p1)
Let me show you, one of the options. For this draft, I'd expect, that records which do have LookupColumn1Id NULL will play the role of the Department, the rest will play the role of Employee.
The Entities could look like this:
public class BaseEntity
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Employee : BaseEntity
{
public virtual Department Department { get; set; } // to lookup record
}
public class Department : BaseEntity
{
public virtual IList<Employee> Employees { get; set; } // the way back
}
The mapping could be like this:
<class name="Department" table="[dbo].[AllData]" lazy="true" batch-size="25"
where="LookupColumn1Id IS NULL" >
<id name="Id" column="Id" generator="native" />
<property not-null="true" name="Name" column="Title" />
<bag name="Employees" >
<key column="LookupColumn1Id" />
<one-to-many class="Employee"/>
</bag>
</class>
<class name="Employee1" table="[dbo].[AllData]" lazy="true" batch-size="25"
where="LookupColumn1Id IS NOT NULL" >
<id name="Id" column="Id" generator="native" />
<property not-null="true" name="Name" column="Title" />
<many-to-one name="Department" class="Department" column="LookupColumn1Id " />
</class>
This mapping, for read access (the required SELECT) is working. Now, we can create a query:
[TestMethod]
public void TestAllData()
{
var session = NHSession.GetCurrent();
// the Employee Criteria
var criteria = session.CreateCriteria<Employee>();
// joined with the Department
var deptCrit = criteria.CreateCriteria("Department", JoinType.LeftOuterJoin);
// here we can filter Department
deptCrit.Add(Restrictions.Eq("Name", "Dep Name"));
// here we can filter Employee
criteria.Add(Restrictions.Eq("Name", "Emp Name"));
// the SELECT
var results = criteria
.List<Employee>();
Assert.IsTrue(results.IsNotEmpty());
var employee = results.First();
// check if all data are injected into our properties
Assert.IsTrue(employee.Name.IsNotEmpty());
Assert.IsTrue(employee.Department.Name.IsNotEmpty());
}
This scenario in general will work, but what we did, is the inheritance only in C# (both derived from BaseEntity), while not in the mapping.
The reason is, the missing column which would play the Discriminator role. That's why we are using the mapping with a WHERE attribute (see class element in xml), distinguishing the Department and Employee by the lookup column presence

Loading a collection of union-subclass entities polymorphically - column specified multiple times

Take the following entities:
public class Company : Entity<Guid>
{
public virtual string Name { get; set; }
public virtual IList<IEmployee> Employees { get; set; }
public Company()
{
Id = Guid.NewGuid();
Employees = new List<IEmployee>();
}
}
public interface IEmployee
{
Guid? Id { get; set; }
string Name { get; set; }
void Work();
Company Company { get; set; }
}
public class ProductionEmployee : Entity<Guid>, IEmployee
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
public ProductionEmployee()
{
Id = Guid.NewGuid();
}
public virtual void Work()
{
Console.WriteLine("I'm making the stuff.");
}
}
public class SalesEmployee : Entity<Guid>, IEmployee
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
public SalesEmployee()
{
Id = Guid.NewGuid();
}
public virtual void Work()
{
Console.WriteLine("I'm selling the stuff.");
}
}
Mapped in the following way in NHibernate:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PolymorphicUnionSubclass.Domain.Entities"
assembly="PolymorphicUnionSubclass.Domain">
<class name="Company" table="`Company`">
<id name="Id" column="Id" type="guid">
<generator class="assigned"/>
</id>
<property name="Name" column="`Name`"/>
<bag name="Employees" inverse="true" cascade="save-update">
<key column="CompanyId"></key>
<one-to-many class="IEmployee" />
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PolymorphicUnionSubclass.Domain.Entities"
assembly="PolymorphicUnionSubclass.Domain">
<class name="IEmployee" abstract="true">
<id name="Id" column="Id" type="guid">
<generator class="assigned"/>
</id>
<many-to-one name="Company" column="`CompanyId`" cascade="save-update"/>
<union-subclass name="ProductionEmployee" table ="`ProductionEmployee`" >
</union-subclass>
<union-subclass name="SalesEmployee" table ="`SalesEmployee`">
</union-subclass>
</class>
</hibernate-mapping>
If I create a Company entity and add IEmployee entities to its collection (also setting the Company property of the IEmployee entity to create the bi-directional relationship), Then when I save the company, everything goes into the database as expected. The companyId is set correctly on the PoductionEmployee and SalesEmlpoyee records.
But when I come to load it, I get the following error:
The column 'CompanyId' was specified multiple times for 'employees0_'
The generated SQL looks like this:
SELECT employees0_.CompanyId as CompanyId1_, employees0_.Id as Id1_, employees0_.Id as Id9_0_, employees0_.[CompanyId] as CompanyId2_9_0_, employees0_.clazz_ as clazz_0_
FROM ( select Id, CompanyId, CompanyId, 1 as clazz_ from [ProductionEmployee] union all select Id, CompanyId, CompanyId, 2 as clazz_ from [SalesEmployee] ) employees0_
WHERE employees0_.CompanyId=?
Why is it generating the CopmanyId column twice and how do I prevent this?
Nothing to do with union-subclass in the end. The problem was in the one-to-many collection I had specified column="CompanyId", and in the many-to-one I had specified column="`CompanyId`". Including the backticks in one and not the other had caused NHibernate to think they were different column. Never come across this in all my time using NHibernate.

How to map an abstract property with NHibernate union subclass?

Referring to Ayende's post here:
http://ayende.com/blog/3941/nhibernate-mapping-inheritance
I have a similar situation that can be reached by extending the union-subclass mapping of the above post a bit, by adding an abstract Name-property to the Party. The model would be as follows:
public abstract class Party
{
public abstract string Name { get; }
}
public class Person : Party
{
public override string Name { get { return this.FirstName + " " + this.LastName; } }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class Company : Party
{
public override string Name { get { return this.CompanyName; } }
public virtual string CompanyName { get; set; }
}
I'm looking for a mapping that would allow me to query over the parties in the following manner:
session.QueryOver<Party>().Where(p => p.Name.IsLike("firstname lastname")).List();
The mapping I'm using:
<class name="Party" table="`party`" abstract="true">
<id access="backfield" name="Id">
<column name="Id" />
<generator class="sequence">
<param name="sequence">party_id_seq</param>
</generator>
</id>
<union-subclass name="Person" table="`person`">
<property name="Name" formula="first_name || ' ' || last_name" update="false" insert="false" access="readonly">
</property>
<property name="FirstName">
<column name="first_name" />
</property>
<property name="LastName">
<column name="last_name" />
</property>
</union-subclass>
<union-subclass name="Company" table="`company`">
<property name="Name" access="readonly" update="false" insert="false">
<column name="company_name" />
</property>
<property name="CompanyName">
<column name="company_name" />
</property>
</union-subclass>
For both
session.QueryOver<Person>().Where(p => p.Name.IsLike("firstname lastname")).List();
and
session.QueryOver<Company>().Where(p => p.Name.IsLike("companyName")).List();
this behaves as I'd expect, and I can query over the name and get the matching results. However, when I do
session.QueryOver<Party>().Where(p => p.Name.IsLike("firstname lastname")).List();
The query doesn't match the Persons at all, but uses the mapping from the union-subclass of the company. So when parametrized with Party, the query seems to be essentially the same as when parametrized with Company (the WHERE-clause of the query is: WHERE this_.company_name = ((E'firstname lastname')::text))
Any pointers about where I might be going wrong and how to achieve what I'm after?
It would be because you were using the logic inside the properties, which NHibernate would be unable to determine. Since you have already defined the formulas for the Name field it's best to keep them as standard properties. So if you correct the entities as follows, it should work
public abstract class Party
{
public abstract string Name { get; }
}
public class Person : Party
{
public virtual string Name { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class Company : Party
{
public virtual string Name { get; set; }
public virtual string CompanyName { get; set; }
}
Apparently this is a known issue of NHibernate 3.x:
https://nhibernate.jira.com/browse/NH-2354?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel

How to do this Unidirectional NHibernate one-to-one mapping?

This is a problem of unidirectional one-to-one mapping in NHibernate.
Student.cs
public class Student
{
public int ID { get; set; }
public int Roll { get; set; }
public int RegNo { get; set; }
public string Name { get; set; }
public StudentDetail StudentDetail { get; set; }
}
StudentDetail.cs
public class StudentDetail
{
public int ID { get; set; }
public string Father { get; set; }
public string Mother { get; set; }
}
How can I map these classes (how do the hbm mapping files look like) to the following case of one-to-one relationship?
Please have a look at the classes and the table very carefully.
Where can I put the <many-to-one> tag in Student.hbm.xml or StudentDetail.hbm.xml? If I put it in Student.hbm.xml, how can I map the column StudentDetail.StudentID, coz it is in a different table?
So this mapping:
<class name="Student" table="Student">
<id name="ID" column="ID">
<generator class="native"/>
</id>
.......
<many-to-one class="StudentDetail" name="StudentDetail" column="StudentID" unique="true" cascade="all" />
</class>
generates the following exception:
{"Invalid column name 'StudentID'."}
On the other hand <many-to-one> can't be placed in StudentDetail.hbm.xml. Coz, StudentDetail.cs doesn't contain any property of type Student.
Can I use <one-to-one>-tag? If yes where should I place it, in Student.cs or StudentDetail.cs? And how should I configure it?
Case #1:
In Student...
<one-to-one name="StudentDetail"
cascade="save-update,delete"
property-ref="Student" />
In StudentDetail...
<many-to-one name="Student"
column="StudentID"
unique="true"
cascade="none" />
Note that you'll have to have a property in your StudentDetail class that refers to a Student oobject (called Student). Also, your cascades might be different depending on your usage. You most likely want the delete cascade in there, though.
The unique="true" ensures the one-to-one mapping on the StudentDetail side.
Case #2:
Just exchange the two mappings, making sure you change the property names to the opposite class.
Look here for more info:
http://nhforge.org/blogs/nhibernate/archive/2009/04/19/nhibernate-mapping-lt-one-to-one-gt.aspx
You can map it as a one-to-many, with the collection property hidden and only its first element publicly exposed:
public class Student
{
public virtual int ID { get; set; }
public virtual int Roll { get; set; }
public virtual int RegNo { get; set; }
public virtual string Name { get; set; }
protected virtual IList<StudentDetail> StudentDetails { get; set; }
public virtual StudentDetail StudentDetail
{
get
{
if (StudentDetails.Count == 0) return null;
return StudentDetails[0];
}
set
{
if (StudentDetails.Count != 0) throw new Exception();
StudentDetails.Add(value);
value.Student = this;
}
}
}
You could handle the setter better than this - the point is to make sure you don't add multiple rows to the one-to-many. Obviously in this, StudentDetails is mapped but StudentDetail isn't in your .hbm.xml or Fluent mappings.

Nhibernate mapping

I am trying to map Users to each other. The senario is that users can have buddies, so it links to itself
I was thinking of this
public class User
{
public virtual Guid Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string Password { get; set; }
public virtual DateTime? DateCreated { get; set; }
**public virtual IList<User> Friends { get; set; }**
public virtual bool Deleted { get; set; }
}
But am strugling to do the xml mapping.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyVerse.Domain"
namespace="MyVerse.Domain" >
<class name="User" table="[User]">
<id name="Id">
<generator class="guid" />
</id>
<property name="FirstName" />
<property name="LastName" />
<property name="EmailAddress" />
<property name="Password" />
<property name="DateCreated" />
<property name="Deleted" />
<set name="Friends" table="UserFriend">
<key foreign-key="Id"></key>
<many-to-many class="User"></many-to-many>
</set>
</class>
</hibernate-mapping>
something like
<bag name="Friends" table="assoc_user_table" inverse="true" lazy="true" cascade="all">
<key column="friend_id" />
<many-to-many class="User,user_table" column="user_id" />
</bag>
Consider using the repository pattern. Create a Repository contract and a base abstract class that takes one of your entities as a type (your mapped class)
Open the session when the repository is initialized and close when destroyed. (implement IDisposable).
Then make sure all of your access to the session happens within the using statement:
[pseudo-code]:
using(var repository = RepositoryFactory<EntityType>.CreateRepository())
{
var entity = repository.get(EntityID);
foreach (somesubclass in entity.subclasscollection)
{
//Lazy loading can happen here, session is still open with the repository
... Do Something
}
}
I use a base abstract class for my Repositories. This one is for my readonly repository but you'll get the drift. They key is to keep your units of work small, open the session only when you have something to do with the database, then let it close on the dispose. Here's the base class, disclaimer YMMV:
public interface IEntity
{
int Id { get; set; }
}
public interface IRORepository<TEntity> : IDisposable where TEntity : IEntity
{
List<TEntity> GetAll();
TEntity Get(int id);
}
public abstract class RORepositoryBase<T> : IRORepository<T> where T : IEntity
{
protected ISession NHibernateSession;
protected RORepositoryBase()
{
NHibernateSession = HibernateFactory.OpenSession();
NHibernateSession.DefaultReadOnly = true;
}
public ISession Session { get { return NHibernateSession; } }
public void Dispose()
{
NHibernateSession.Flush();
NHibernateSession.Close();
NHibernateSession.Dispose();
}
public virtual List<T> GetAll()
{
return NHibernateSession.Query<T>().ToList();
}
public virtual T Get(int id)
{
return NHibernateSession.Get<T>(id);
}
}