How to setup Fluent NHibernate Many-To-Many automapping using Set instead of Bag? - fluent-nhibernate

http://www.codinginstinct.com/2010/03/nhibernate-tip-use-set-for-many-to-many.html
I want to do the way the author suggested for fluent nhibernate many-to-many but use automapping instead of HBM file.
Here are my two entities
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Iesi.Collections.Generic.Set<City> Cities { get; set; }
}
public class City{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Iesi.Collections.Generic.Set<User> Users { get; set; }
}
Tried with HashSet, IList, and Set. But when I looked at the HBM files generated by calling automapping output method:
var autoMappings = new AutoPersistenceModel().AddEntityAssembly(entityAssembly).Where(x => x.Namespace.EndsWith("Domain"));
autoMappings.WriteMappingsTo((#"C:\TEMP");
It's still bag type
<bag inverse="true" name="Users" table="MNUserCity" mutable="true">
<key>
<column name="CityId" />
</key>
<many-to-many class="MyApp.Entity.Domain.User, MyApp.Entity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<column name="UserId" />
</many-to-many>
</bag>
Is there any conventions/override I can use in Fluent NHibernate to alter the collection type for all the ManyToMany in the app domain? I looked at IHasManyToMany convention but no clue.
Anyone can help? Thanks.
BTW, I'm using latest build in http://github.com/jagregory/fluent-nhibernate

Changing the type of Users and Cities from Set to ISet should solve your issue.
As stated by James in this thread, "The automapper is very opinionated and inflexible, and it expects collections to be exposed as IList or ISet."

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.

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.

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.

Complicated NHibernate component mapping

EDIT: I simplified the problem to leave only what really bothers me.
Hello all,
I am trying to make the following mapping.
In my database, I have a table called "ReportRowValue" containg the following columns:
RowNumber
ColumnNumber
StringValue
LongValue
DateValue
Value
In my code I want to get a more usable structure by creating several two classes from this one table. I guess this should be done using components and inheritance but I did not managed to create a working mapping file. What I want in code should look like this:
ReportRow
RowNumber
Values (collection of ReportValue below)
ReportValue (being an abstract class)
ColumnNumber
Value
ReportValueString / ReportValueLong / ReportValueDate (each one inheriting from ReportValue)
Value (each one having a Value property of its one type)
And that's about all!
Does anyone can point me how to create an nhibernate mapping file/files for doing that?
Thanks,
Meigetsu
There is couple of tools that maps and builds class for you one of them is
mygeneration
is the software http://sourceforge.net/projects/mygeneration/
In this page you find the templates that you need to run with the
softwarehttp://www.mygenerationsoftware.com/TemplateLibrary/Archives/?query=nhibernate
After you have this in the mygeneration tool you only connect to your DB and it will generated for you
Unfortunately, you can't have a polymorphic structure in a component. But I'm acutally not sure if you need it.
The following code is straight from my head, so it certainly has errors or missing things and wouldn't compile. But it should show the direction:
public class ReportRow
{
public int Id { get; private set; }
public IList<IReportValue> Values { get; private set; }
}
public interface IReportValue
{
public int Id{ get; set; }
public object UntypedValue { get; }
}
public abstract class ReportValue<T> : IReportValue
{
public int Id{ get; set; }
public T Value { get; set; }
public object UntypedValue { get { return Value; } }
}
public class ReportLongValue : ReportValue<long> {}
public class ReportStringValue : ReportValue<string> {}
public class ReportDateValue : ReportValue<DateTime>{}
Mapping:
<class ReportRow>
<id ...>
<bag name="Values" >
<key column="RowNumber"/>
<one-to-many class="IReportValue"/>
</bag>
</class>
<class name="IReportValue" abstract="true">
<id ...>
<subclass name="ReportLongValue">
<property name="Value" column="LongValue"/>
</subclass>
<subclass name="ReportStringValue">
<property name="Value" column="StringValue"/>
</subclass>
<subclass name="ReportDateValue">
<property name="Value" column="DateValue"/>
</subclass>
</class>

How can I use a composite-id with a class as the id field in fluent nhibernate?

I've got a mapping file like this:
<class name="Resource" table="resource" discriminator-value="null">
<composite-id name="Key" class="Models.Bases.ClientKey, Models">
<key-property name="Id" column="ID" type="int"/>
<key-property name="SiteId" column="clientID" type="int"/>
</composite-id>
<property name="Name" type="String">
<column name="`name`" length="500" sql-type="varchar" not-null="false"/>
</property>
</class>
which works just fine and here's the id class:
public class ClientKey
{
public int Id { get; set; }
public int ClientId { get; set; }
}
public class Resource
{
public virtual ClientKey Key { get; set; }
public virtual string Name { get; set; }
}
How can I remap this using FluentNhibernate? This code doesn't work:
WithTable("resource");
UseCompositeId()
.WithKeyProperty(x => x.Key.Id, "ID")
.WithKeyProperty(x => x.Key.ClientId, "clientID");
Map(x => x.Name);
It throws this error:
Could not find a getter for property 'Id' in class 'Models.Resource'
Thanks!!!
I'm afraid you can't fix it without modifying Resource class. I have checked with Fluent NHibernate's source - here's what code that outputs composite-id part looks like:
XmlElement element = classElement.AddElement("composite-id");
foreach( var keyProp in keyProperties )
{
keyProp.Write(element, visitor);
}
What is missing is "name" attribute, which should be set to "Key". Without this attibute, NHibernate fallbacks to default property name = "Id". As your class doesn't have Id property, NHibernate doesn't know what to do and throws an exception.
If you can't modify Resource class, you would have to use hbm mapping for this class or create a patch to fluent nhibernate (it is possible that this is known issue and someone's working on it - refer to fluent nhibernate's issue tracker).