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.
Related
We are working on a domain where the requirement is that a MemberAgreement varies based on Network and State combination. MemberAgreement has a Template which can be shared by multiple States for a Network.
The way we are modelling the Entities is
public class MemberAgreement
{
public Network Network { get; protected set; }
public List<State> States { get; protected set; }
public Template Template {get; protected set; }
}
The tables are designed as:
Agreement
---------
Id
NetworkId
StateId
TemplateId
In this table Agreement, NetworkId and TemplateId can repeat for different StateIds.
Now, how do I map this in Fluent NHibernate? We have One to Many relation between Network and State and combination of these two has Many to Many relation with Template.
Help is appreciated.
PC
The table structure as is will force you to change your MemberAgreement object definition. Every row (unique by ID) is referencing exactly one ReferenceType:
public class MemberAgreement
{
public virtual Network Network { get; protected set; }
// public List<State> States { get; protected set; }
public virtual State State { get; protected set; }
public virtual Template Template {get; protected set; }
}
Having this, your mapping will be simplified References(x => x.State). But more issues will come in your business layer, where you have to work with collection: IList<MemberAgreement>.
Other option is to use 6.9. Ternary Associations. Extracted and applied on a Network:
public class Network
{
...
public virtual IDictionary<State, Template> StateTemplates { get; set; }
}
<map name="States" table="Agreement" >
<key column="NetworkId" />
<index-many-to-many column="StateId" class="State" />
<many-to-many column="TempalteId" class="Template" />
</map>
and in Fluent like this :
HasMany(x => x.StateTemplates)
.KeyColumn("NetworkId")
.AsMap<State>("StateId")
.AsTernaryAssociation("TemplateId");
Is it possible to map two classes to the same property without them sharing a common base class?
For example, a situation like this:
class Rule
{
public virtual int SequenceNumber { get; set; }
public virtual ICondition Condition { get; set; }
}
interface ICondition
{
}
class ExpressionCondition : ICondition
{
public virtual string Expression { get; set; }
}
class ThresholdCondition : ICondition
{
public virtual int Threshold { get; set; }
}
I also cannot add some empty abstract class that both conditions inherit from because the two ICondition implementations exist in different domains that are not allowed to reference each other. (Please no responses telling me that this situation should not occur in the first place - I'm aware of it and it doesn't help me.)
using FluentNHibernate
ReferencesAny(x => x.Condition)
.EntityIdentifierColumn("cond_Id")
.EntityTypeColumn("condType")
.IdentityType<int>()
.AddMetaValue<ExpressionCondition>("express")
.AddMetaValue<ThresholdCondition >("threshold");
using xml
<any id-type="System.Int32" meta-type="System.String" name="Condition">
<meta-value value="express" class="Namespace.ExpressionCondition" />
<meta-value value="threshold" class="Namespace.ThresholdCondition" />
<column name="condType" />
<column name="cond_Id" />
</any>
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>
I have two similar tables (Table1, Table2) so I created a base abstract class which has common properties. Each table has a column indicating status of record processing. I'd like to map this columns to one enum:
enum RecordStatus
{
UnkownStatus,
NotProcessed,
Processed,
}
Unfortunately for each table I need mapping different values for enums.
So I created two converters (Table1StatusConverter, Table2StatusConverter) which inherit from EnumType<RecordStatus> and setup in mappings. It works partially. Partially because NHibernate use only one converter in both classes.
Is this bug or maybe it works like described by design? Is there any workaround for this?
Edit: I write code from memory because the moment I do not have access to it
Entities:
class abstract TableBase
{
public Guid Id { get; protected set; }
public string Sender { get; protected set; }
public DateTime ReceiveTime { get; protected set; }
public RecordStatus Status { get; set; }
}
class Table1 : TableBase
{
public string Message { get; set; }
}
class Table2 : TableBase
{
public ICollection Parts { get; protected set; }
}
Converters: Table1StatusConverter and Table2StatusConverter override the same method, but in different ways.
class Table1StatusConverter : EnumType<RecordStatus>
{
public override object GetValue(object enumValue) { ... }
public override object GetInstance(object value) { ... }
}
Mappings:
Table1.hbm.xml
<class name="Table1" table="Table1">
..
<property name="Status" type="MyAssembly.Table1StatusConverter, MyAssembly" />
..
</class>
Table2.hbm.xml
<class name="Table2" table="Table2">
..
<property name="Status" type="MyAssembly.Table2StatusConverter, MyAssembly" />
..
</class>
This doesn't sound like a good use of inheritance. However, you could accomplish this by mapping the integer value for the enums as a protected field in the base class and use public properties in the extended classes to cast to and from the appropriate enum.
May be you need to override this properties explicitly?
class abstract TableBase
{
// ...
public virtual RecordStatus Status { get; set; }
}
class Table1 : TableBase
{
public string Message { get; set; }
public override RecordStatus Status { get; set; }
}
class Table2 : TableBase
{
public ICollection Parts { get; protected set; }
public override RecordStatus Status { get; set; }
}
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.