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

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.

Related

Issue when quering Hierarchy to Hierarchy relationship

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.

Insert in two related table at once with Nhibernate

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.

When using DTOs, Automapper & Nhibernate reflecting changes in child collections of DTO in domain object being updated

I'm not massively familiar with this design but I am hoping to get some guidance.
I have a backend service that sends out DTOs to a WPF smart client. On the WPF smart client the user will change,delete and modify items and then the changes are sent back (client --> server). As an example, currently I am working on the Customer details form and the user has the ability to add,remove and change categories belonging to a customer in a datagrid. When the DTO is sent back to the server I would like to load in the domain object that is related to the ID in the DTO and apply the changes made on the DTO to the domain object, including all the child collections.
I have made an attempt at doing something similar to this in the code below with the UpdateCustomer method. However, I think I am way off the mark. When the code runs instead of ending up with a list of {Individual,Company,NGO,Government} I end up with a list of {Individual,B2B,Company,NGO,Government} as it has clearly not deleted the B2B entry from the original list.
One option that has occurred to me is to loop through the DTO collection and compare it to the collection from the domain object and add, remove and update dependent on what has been modified. However, this seemed really cumbersome.
What do I need to do to apply the changes from the DTO to the child collections in my domiain object?
Thank you very much for any assistance it will be thoroughly appreciated
Alex
public class Customer
{
public virtual int Id { get; set; }
public virtual IList<Category> Categories { get; private set; }
public virtual string Code { get; set; }
public virtual string Description { get; set; }
public Customer()
{
Categories = new List<Category>();
}
public virtual void AddCategory(string categoryName)
{
Categories.Add(new Category(categoryName));
}
}
public class Category
{
public virtual string CategoryName { get; private set; }
public virtual Customer Customer {get;set;}
public virtual int Id { get; set; }
protected Category(){}
public Category(string name)
{
CategoryName = name;
}
}
}
public void SetUpAutoMapper()
{
Mapper.CreateMap<Category, CategoryDto>();
Mapper.CreateMap<Customer, CustomerDto>();
Mapper.CreateMap<CategoryDto, Category>();
Mapper.CreateMap<CustomerDto, Customer>();
Mapper.AssertConfigurationIsValid();
}
public void SaveCustomer()
{
var customer = new Customer{Code="TESTCUST",Description="TEST CUSTOMER"};
customer.AddCategory("Individual");
customer.AddCategory("B2B");
customer.AddCategory("Healthcare");
customer.AddCategory("NGO");
repository.Save(customer);
}
public CustomerDto GetCustomer(int customerId)
{
var customer = repository.GetCustomer(customerId);
var customerDto = Mapper.Map<Customer,CustomerDto>(customer);
return customerDto;
}
public void UpateCustomer(CustomerDto customerToUpdate)
{
/*imagine that the dto incoming has had the following operations performed on it
-----add new category----
customerToUpdate.Categories.Add(new CategoryDto {CategoryName = "Government"});
---update existing category---
customerToUpdate.Categories[2].CategoryName = "Company";
---remove category---
customerToUpdate.Categories.RemoveAt(1);*/
var customer = repository.GetCustomer(customerToUpdate.Id);
/* How in this bit do I ensure that the child collection changes are
propogated into the underlying customer object retrieved from the database*/
var customer = Mapper.Map<CustomerDto,Customer>(customerToUpdate);
repository.Save(customer);
}
public class CustomerDto
{
public int Id { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public List<CategoryDto> Categories { get; set; }
}
public class CategoryDto
{
public int Id { get; set; }
public string CategoryName { get; set; }
}
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Customer" table="Customer">
<id name="Id" column="CustomerId">
<generator class="native"/>
</id>
<property name="Code" />
<property name="Description" />
<bag name="Categories" table="Categories" cascade="all" inverse="false">
<key column="FK_CustomerID" />
<one-to-many class="Category"/>
</bag>
</class>
<class name="Category" table="Categories">
<id name="Id" column="CategoryId">
<generator class="native"/>
</id>
<many-to-one name="Customer" column="FK_CustomerId" not-null="true" class="Customer"></many-to-one>
<property name="CategoryName" />
</class>
</hibernate-mapping>
I recently did something similar but with EF as the datatier. I don't know nhibernate to know if the same approach would work.
Basic steps were
Ensure the destination collection is loaded from db and attached to the object graph for change tracking
.ForMember(dest => dest.Categories, opt => opt.UseDestinationValue())
Then create a custom IObjectMapper for mapping IList<> to IList<T> where T : Entity
The custom IObject mapper used some code from http://groups.google.com/group/automapper-users/browse_thread/thread/8c7896fbc3f72514
foreach (var child in source.ChildCollection)
{
var targetChild = target.ChildCollection.SingleOrDefault(c => c.Equals(child)); // overwrite Equals or replace comparison with an Id comparison
if (targetChild == null)
{
target.ChildCollection.Add(Mapper.Map<SourceChildType, TargetChildType>(child));
}
else
{
Mapper.Map(child, targetChild);
}
}
Finally one last piece of logic to check all Id's in targetCollection exist in sourceCollection and delete them if they don't.
It wasn't all that much code in the end and is reusable in other actions.
Mapper.CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.Categories, opt => opt.MapFrom(src =>src.Categories));
or
Mapper.CreateMap<IList<Category>, IList<CategoryDto>>();
something like this to tell automapper to map the list, too.

NHibernate multiple column mapping collection

I have this situation:
I have several tables table_1, table_2... table_n, they belong to different data but they have some fields in common, record_id, form_id where id is the primary key. All these tables are represented by a single class Record (Id, Form, Attributes)
I have another table tbl_attachments which have attachment_id, record_id, form_id (record_id is not enough because the record_id can be repeated over table_X tables.
The problem I have is I want to have property Attachments in class Record, to get the records attachments from tbl_attachments if any.
Can you help me with the mapping on nhibernate to get this done?
I appreciate any help.
Edit: Forgot to say that a Record on table_X can have multiple attachments :)
you can achieve this using AnyMapping
abstract class RecordBase
{
public virtual int Id { get; set; }
public virtual int FormId { get; set; }
public virtual ICollection<Attachment> Attachments { get; set; }
}
class RecordA : RecordBase
{ }
class Attachment
{
public virtual int id { get; set; }
public virtual RecordBase Record { get; set; }
}
class RecordAMap : ClassMap<RecordA>
{
public RecordAMap()
{
HasMany(x => x.Attachments)
.Where("Form_Id = 5");
}
}
class AttachmentMap : ClassMap<Attachment>
{
public AttachmentMap()
{
ReferencesAny(x => x.Record)
.EntityIdentifierColumn("record_id")
.EntityTypeColumn("form_id")
.IdentityType<int>()
.AddMetaValue<RecordA>("5");
}
}
UPDATE: xml mappings
<bag where="form_id=5">
<key column="record_id"/>
<one-to-many class="Attachment1"/>
</bag>
<any name="Item" id-type="System.Int32" meta-type="System.Int32">
<column name="form_id" />
<column name="record_id" />
</any>

NHibernate Automapping problem

Recently I came across a strange behavior in Automapping of Fluent NHibernate. I have the following class structure (some properties cut off for the sake of brewity).
public class UserGroup
{
public virtual UserGroup ParentGroup { get; set; }
public virtual UserGroupMember Manager { get; protected set; }
public virtual ISet<UserGroupMember> Members { get; protected set; }
}
and
public class UserGroupMember : BaseEntity
{
public virtual User User { get; set; }
public virtual UserGroup Group { get; set; }
}
The mapping for UserGroup:
public class UserGroupMap : IAutoMappingOverride<UserGroup>
{
public void Override(AutoMapping<UserGroup> mapping)
{
mapping.HasMany(el => el.Members)
.Cascade
.AllDeleteOrphan().Inverse().LazyLoad();
}
}
The automapping creates two column (both of which are foreign keys) in the UserGroupMember table to reflect the relation between UserGroup and UserGroupMembers. I've found out that the generated mapping contains wrong column (as seen below):
<set cascade="all-delete-orphan" inverse="true" lazy="true" name="Members" mutable="true">
<key>
<column name="Parent_Id" />
</key>
<one-to-many class="Groups.Data.UserGroupMember, Server, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</set>
which results in wrong queries:
While insert in UserGroupMember - Group_Id is used (which is right), not using Parent_Id
While select in UserGroupMember - Parent_Id is used
Group_Id is the column in UserGroupMember mapping file which reflects the Group property in UserGroupMember.
I tried to modify the mapping adding .KeyColumn("Group_Id") and it is solves the problem. But is there any way to make Fluent NHibernate 'think the right way'?
This is from memory, as I don't have test code ready.
When using bidirectional many-to-many, you sometimes have to help FHN figure columns names, if they're not "alike" on both sides.
For example this should map correcly
public class User
{
public IList<Group> Groups { get; set; }
}
public class Group
{
public IList<User> Users { get; set; }
}
While this would not
public class User
{
public IList<Group> BelongsTo { get; set; }
}
public class Group
{
public IList<User> Contains { get; set; }
}
As a rule of thumb, if automapping (with or without conventions) doesn't generate right columns names, especially for non trivial cases, do not hesitate to put an override to set those column names manually.