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
Related
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.
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.
I have two entities like
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int DataId { get; set; }
}
public class Data
{
public int DataId { get; set; }
public string details { get; set; }
public int PersnId{ get; set; }
}
as you see both table are relate to each other. I want a solution to insert data in both table at once. I 1-insert person, 2-insert data and then update person and it works but I'm looking for way to eliminate Update.
My mapping for person table:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="TestNhibrinate" assembly="TestNhibrinate">
<class name="TestNhibrinate.Entites.Person" table="Person" lazy="false">
<id name="PersonId" column="PersonId" type="int" >
<generator class="identity" />
</id>
<property name="Name" column="Name" type="String" length="50" />
<many-to-one name="Adress" class="TestNhibrinate.Entites.Adress" column="AdressId"/>
</class>
</hibernate-mapping>
and same mapping for data.
You entities should look like this:
public class Person
{
public Person()
{
DataCollection = new List<Data>();
}
public int PersonId { get; set; }
public string Name { get; set; }
public int DataId { get; set; }
public IList<Data> DataCollection{get;set;}
public void AddData(Data item)
{
if(!DataCollection.Contains(item))
{
DataCollection.Add(item);
}
}
}
public class Data
{
public int DataId { get; set; }
public string details { get; set; }
public Person Person{ get; set; }
}
This way you create a one-to-many relation from Person to Data. If you save your person entity after you added some data, the data will also be persisted. This depends on your cascade options offcourse.
I'm not sure how to map this with XML mappings, since i always use Fluent or Auto mappings.
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
I use HBM mapping.
I have tables :
I) person with columns :
1. ID
2. TYPE
3.CREATE_DATE
4.UPDATE_DATE
II) Attribute with columns:
1.ID
2.TYPE(in this example person may be all type)
3.NAME
4.CREATE_DATE
5.UPDATE_DATE
III) Attribute_VALUE with columns:
1.ID
2.VALUE
4.OBJECT_ID
5.ATTRIBUTE_ID
6.CREATE_DATE
7.UPDATE_DATE
There is relationship one-to-many between person(ID) and Attribute_VALUE(OBJECT_ID).
There is relationship one-to-many between Attribute(ID) and Attribute_VALUE(ATTRIBUTE_ID)
I need build object PERSON that contain all columns of person and dictionary with name attribute.
The dictionary contain key - name of attribute value- collection of values .
Can I build appropriate HBM ??
the short answer no.
the long answer:
consider how should nhibernate match attributes when you Attributes.Add("foo", "value")? it has to search the db for an attribute foo (which is not a simple mapping, its logic) or it would create a new Attribute, everytime you add one.
So given the above schema you either a) have some kind of custom onsave code (which i think is a lot of effort) or b) you change the Person to
class Person
{
public virtual int Id { get; set; }
public virtual ICollection<AttributeValue> Attributes { get; set; }
public virtual IEnumerable<string> GetValues(string attributeName)
{
return Attributes
.Where(attr => attr.Attribute.Name == attributeName)
.Select(attr => attr.Value);
}
public virtual void AddValue(Attribute attribute, string value)
{
Attributes.Add(new AttributeValue
{
Attribute = attribute,
Value = value
});
}
public virtual IEnumerable<string> GetAttributeNames()
{
return Attributes
.Select(attr => attr.Attribute.Name);
}
}
class Attribute
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
// and more Properties like created and updated
}
class AttributeValue
{
public virtual int Id { get; set; }
public virtual Attribute Attribute { get; set; }
public virtual string Value { get; set; }
// and more Properties like created and updated
}
and then use
<class name="Person" table="Persons" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="ID"/>
<bag name="Attributes">
<key column="OBJECT_ID"/>
<one-to-many class="AttributeValue"/>
</bag>
</class>
<class name="Attribute" table="Attributes" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="ID"/>
<property name="Name" column="Name"/>
<!--additional properties-->
</class>
<class name="AttributeValue" table="AttributeValues" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="ID"/>
<many-to-one class="Attribute" column="ATTRIBUTE_ID"/>
<property name="Value" column="Value"/>
<!--additional properties-->
</class>