Edit: changed class names.
I'm using Fluent NHibernate (v 1.0.0.614) automapping on the following set of classes (where Entity is the base class provided in the S#arp Architecture framework):
public class Car : Entity
{
public virtual int ModelYear { get; set; }
public virtual Company Manufacturer { get; set; }
}
public class Sedan : Car
{
public virtual bool WonSedanOfYear { get; set; }
}
public class Company : Entity
{
public virtual IList<Sedan> Sedans { get; set; }
}
This results in the following Configuration (as written to hbm.xml):
<class name="Company" table="Companies">
<id name="Id" type="System.Int32" unsaved-value="0">
<column name="`ID`" />
<generator class="identity" />
</id>
<bag cascade="all" inverse="true" name="Sedans" mutable="true">
<key>
<column name="`CompanyID`" />
</key>
<one-to-many class="Sedan" />
</bag>
</class>
<class name="Car" table="Cars">
<id name="Id" type="System.Int32" unsaved-value="0">
<column name="`ID`" />
<generator class="identity" />
</id>
<property name="ModelYear" type="System.Int32">
<column name="`ModelYear`" />
</property>
<many-to-one cascade="save-update" class="Company" name="Manufacturer">
<column name="`CompanyID`" />
</many-to-one>
<joined-subclass name="Sedan">
<key>
<column name="`CarID`" />
</key>
<property name="WonSedanOfYear" type="System.Boolean">
<column name="`WonSedanOfYear`" />
</property>
</joined-subclass>
</class>
So far so good! But now comes the ugly part. The generated database tables:
Table: Companies
Columns: ID (PK, int, not null)
Table: Cars
Columns: ID (PK, int, not null)
ModelYear (int, null)
CompanyID (FK, int, null)
Table: Sedan
Columns: CarID (PK, FK, int, not null)
WonSedanOfYear (bit, null)
CompanyID (FK, int, null)
Instead of one FK for Company, I get two!
How can I ensure I only get one FK for Company? Override the automapping? Put a convention in place? Or is this a bug? Your thoughts are appreciated.
I feel your pain.
I did the following at this point in time because i was running out of time
public class JoinedSubclassConvention : IJoinedSubclassConvention
{
public void Apply(IJoinedSubclassInstance instance)
{
switch (instance.EntityType.Name)
{
case "Business":
instance.Key.ForeignKey("FK_Business_Customer");
break;
case "Person":
instance.Key.ForeignKey("FK_Person_Customer");
break;
case "StaffMember":
instance.Key.ForeignKey("FK_StaffMember_Customer");
break;
}
}
}
Related
I am trying to map a legacy database. I need to implement what I believe should be a discriminator. My problem is discriminators only seem to work when there is a column to differentiate or a formula on the current row. For my case there is no actual differentiator, the data is either joined to one table if it exists, if not, then the other table. To make things even more complicated the table uses a composite key.
Here's an example (it might be oversimplified as I am making it up):
Given my code
public class SomeTable {
public virtual int DataID { get; set; }
public virtual int EmployeeOrCustomer { get; set; }
public virtual Person Person { get; set; }
public virtual int SomeValue { get; set; }
}
public abstract class Person {
public virtual int ID { get; set; }
public virtual string Name { get; set; }
}
public class Employee : Person {
public virtual int EmployeeNumber { get; set; }
}
public class Customer : Person {
public virtual int CustomerNumber { get; set; }
}
And my Data
-SomeTable-
DataID(K) EmployeeOrCustomer(K) SomeValue
1 1 100
1 22 222
-Employee-
ID Name EmployeNumber
1 Joe Blow 12345
-Customer-
ID Name CustomerNumber
22 ACME Inc. 4242
My Mappings:
<class name="SomeTable" abstract="true">
<composite-id>
<key-property name="DataID" />
<key-property name="EmployeeOrCustomer" />
</composite-id>
<property name="SomeValue" />
<!-- ?????? -->
</class>
<class name="Employee">
<id name="ID" />
<Property name="EmployeeNumber">
</Class>
<class name="Customer">
<id name="ID" />
<Property name="CustomerNumber">
</Class>
What I expect
DataID: 1, SomeValue: 100 Person: { Employee: EmployeeNumber: 12345 }
DataID: 1, SomeValue: 222, Person: { Customer: CustomerNumber: 4242 }
I thought about joining the 2 tables and using the merged tables as a join but the 2 tables differ a lot. Also, I can't modify the schema so adding a discriminator column is not an option.
Any ideas?
i think what you are searching for is union subclass inheritance mapping
<class name="Person" abstract="true">
<id name="Id">
<generator class="assigned"/>
</id>
<union-subclass table="Employee" name="Employee">
<property name="Name"/>
<property name="EmployeeNumber"/>
</union-subclass>
<union-subclass table="Customer" name="Customer">
<property name="Name"/>
<property name="CustomerNumber"/>
</union-subclass>
</class>
<class name="SomeTable" abstract="true">
<composite-id>
<key-property name="DataID" />
<key-many-to-one name="Person" column="EmployeeOrCustomer" />
</composite-id>
<property name="SomeValue" />
</class>
Note the extra property EmployeeOrCustomer is not needed.
I have a Class that has two bags.
One bag is with a collection of a class and it works, the other is a collection of long values and this one is not persisted.
I've searched all the web for this and my mappings appears to be OK.
In my mapping I have this:
<class name="Event" table="Events">
<id name="Id" type="Int32">
<generator class="native" />
</id>
<property name="Name" />
<property name="Owner" />
<many-to-one name="DeliveryAddress" column="DeliveryAddressId" cascade="save-update, persist" />
<many-to-one name="EventAddress" column="EventAddressId" cascade="save-update, persist" />
<bag name="Friends" table="Event_Friends" lazy="false" inverse="true" cascade="save-update, persist" fetch="join">
<key column="EventId" />
<element column="Friend" type="Int64" />
</bag>
<bag name="Products" table="Event_Products" lazy="false" inverse="true" cascade="all,delete-orphan" fetch="join">
<key column="EventId" />
<one-to-many class="Product" />
</bag>
</class>
When I call SabeOrUpdate in my session NHibernate create both adresses, create the event and all the products, but the friends list is not saved.
After the save I issue an Get, and the select on the database is correct.
I don't know what else can be.
My model for this mapping is this:
public class Event
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Int64 Owner { get; set; }
public virtual Address DeliveryAddress { get; set; }
public virtual Address EventAddress { get; set; }
public virtual ICollection<Int64> Friends { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
and my database looks like this:
Events
-------------------------------
Id int identity
Name varchar
Owner long
DeliveryAddressId int
EventAddressId int
Address
-------------------------------
Id int
-- Code Abbreviated --
Event_Products
-------------------------------
Id int
EventId int
-- Code Abbreviated --
Event_Friends
-------------------------------
EventId int
Friend long
You should change inverse to false for your collection of longs
<bag name="Friends" table="Event_Friends" lazy="false" inverse="false" cascade="save-update, persist" fetch="join">
<key column="EventId" />
<element column="Friend" type="Int64" />
</bag>
I have an inheritance
public abstract class UserEntity : Entity
{
public virtual int Id { get; protected set; }
}
public class Employee : UserEntity
{
public virtual string Email { get; set; }
}
Entity is a standard for NH class where overridden methods Equals, GetHashCode, etc. And I use AutMap rewriting .IncludeBase()
I got with Fluent NHibernate Automapping
<class xmlns="urn:nhibernate-mapping-2.2" name="Dto.Entities.UserEntity" table="UserEntities">
<id name="Id" type="System.Int32">
<column name="Id" />
<generator class="identity" />
</id>
<joined-subclass name="Dto.Entities.Employee" table="Employees">
<key foreign-key="FK_Employee_UserEntity">
<column name="UserEntityId" />
</key>
<property name="Email" type="System.String">
<column name="Email" />
</property>
</joined-subclass>
</class>
I want to change name for key column in joined subclass from UserEntityId to EmployeeId
I try
public class UserEntityOverride : IAutoMappingOverride<UserEntity>
{
public void Override(AutoMapping<UserEntity> mapping)
{
mapping.JoinedSubClass<Employee>("EmployeeId");
}
}
but didn't have success.
I use the latest for this moment FNH from NuGet package: FluentNHibernate 1.2.0.712.
Also I have much more configuration and conventions that can somehow affect on this configuration ignoring, but I have tried it on a clear solution with the same negative result.
I have a table called person_skills like so:
person_id, skill_type_id, base_score, misc_score
There is a lookup table that contains id, name for skill_types.
Now the tricky thing is that I have a composite key for person_id, skill_type_id. There will be many entries within this table as a person may have 5 skills.
Currently I have got a class like so:
public class skill
{
int BaseScore {get;set;}
int MiscScore {get;set;}
}
Then I have a class to contain all this like below:
public class person_skills
{
int person_id {get;set;}
IDictionary<skill_type, skill> skills {get;set;}
}
Now im not sure if this is the best way to handle this relationship, ultimately I need to be able to give people a link to skills, there is one person to many skills.
I was thinking about just putting in an auto incrememnt id column and use that as the PK, but it doesn't seem ideal. I can change the models and the DB if required, but as this is used within an ajax part of a page I need to be able to change the skills and then update them into the database.
I did not find an actual question but I'll answer anyway. :)
You do not need a surrogate key for the person_skills table. Your composite key, consisting of person_id and skill_type_id, should be sufficient. I believe the following classes and mappings reflect what you are trying to accomplish here.
Classes:
public class Person
{
public virtual int PersonId { get; set; }
public virtual String Name { get; set; }
public virtual IList<PersonSkills> Skills { get; set; }
}
public class SkillType
{
public virtual int SkillTypeId { get; set; }
public virtual String SkillName { get; set; }
public virtual IList<PersonSkills> Persons { get; set; }
}
public class PersonSkills
{
public virtual int PersonId { get; set; }
public virtual int SkillTypeId { get; set; }
public virtual int BaseScore { get; set; }
public virtual int MiscScore { get; set; }
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj == null || !(obj is PersonSkills))
{
return false;
}
PersonSkills o = obj as PersonSkills;
return (this.PersonId == o.PersonId
&& this.SkillTypeId == o.SkillTypeId);
}
public override int GetHashCode()
{
int hash = 13;
hash = hash + this.PersonId.GetHashCode();
hash = hash + this.SkillTypeId.GetHashCode();
return hash;
}
}
Mappings: (FluentNhibernate)
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.PersonId);
Map(x => x.Name);
HasMany(x => x.Skills)
.KeyColumn("PersonId")
.Cascade.All();
}
}
public class SkillTypeMap : ClassMap<SkillType>
{
public SkillTypeMap()
{
Id(x => x.SkillTypeId);
Map(x => x.SkillName);
HasMany(x => x.Persons)
.KeyColumn("SkillTypeId")
.Cascade.All();
}
}
public class PersonSkillsMap : ClassMap<PersonSkills>
{
public PersonSkillsMap()
{
CompositeId()
.KeyProperty(x => x.PersonId)
.KeyProperty(x => x.SkillTypeId);
Map(x => x.BaseScore);
Map(x => x.MiscScore);
}
}
Mappings (hbm, generated by FluentNHibernate - I removed output that is not required):
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
<class xmlns="urn:nhibernate-mapping-2.2" name="Person" table="Person">
<id name="PersonId" type="int">
<column name="PersonId" />
<generator class="identity" />
</id>
<bag cascade="all" name="Skills" mutable="true">
<key>
<column name="PersonId" />
</key>
<one-to-many class="PersonSkills" />
</bag>
<property name="Name" type="String">
<column name="Name" />
</property>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
<class xmlns="urn:nhibernate-mapping-2.2" name="SkillType" table="SkillType">
<id name="SkillTypeId" type="int">
<column name="SkillTypeId" />
<generator class="identity" />
</id>
<bag cascade="all" name="Persons">
<key>
<column name="SkillTypeId" />
</key>
<one-to-many class="PersonSkills" />
</bag>
<property name="SkillName" type="String">
<column name="SkillName" />
</property>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" >
<class xmlns="urn:nhibernate-mapping-2.2" name="PersonSkills" table="PersonSkills">
<composite-id mapped="false" unsaved-value="undefined">
<key-property name="PersonId" type="int">
<column name="PersonId" />
</key-property>
<key-property name="SkillTypeId" type="int">
<column name="SkillTypeId" />
</key-property>
</composite-id>
<property name="BaseScore" type="int">
<column name="BaseScore" />
</property>
<property name="MiscScore" type="int">
<column name="MiscScore" />
</property>
</class>
</hibernate-mapping>
I have the following entity:
public class Alert
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IDictionary<CxChannel, string> Messages { get; set; }
}
public enum CxChannel
{
Message,
Email
}
and following mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Entities.Alert, Entities" table="alert">
<id name="Id" type="int" unsaved-value="0" access="property">
<generator class="identity"/>
</id>
<property name="Name" column="name"/>
<map name="Messages" table="alert_message" cascade="all">
<key column="alert_id"/>
<index column="channel" type="Entities.CxChannel, Entities"/>
<element column="message" type="System.String"/>
</map>
</class>
</hibernate-mapping>
The problem is that when I save an alert entity, Messages dictionary is not persisted to database. As a matter of fact my code looks like the code Oren used in his blog post: http://ayende.com/Blog/archive/2009/06/03/nhibernate-mapping-ndash-ltmapgt.aspx
Has anyone experienced same issue?
Verify that your channel column is an integer in your schema as the CxChannel enum will be mapped as 0 (for Message) and 1 (for email). I just pasted your code and mappings into a console project, used new SchemaExport(cfg).Execute(false, true, false), and successfully inserted rows into the generated database.