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>
Related
public class Company_Product
{
public virtual Int32 Id { get; set; }
public virtual DateTime SalesDate { get; set; }
public virtual Company Company{ get; set; }
public virtual Product Product { get; set; }
}
public class Company
{
public virtual Int32 Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Company_Product> company_product { get; set; }
}
public class Product
{
public virtual Int32 Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Company_Product> company_product { get; set; }
}
Company.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="BusinessObjectApp"
namespace="BusinessObjectApp.Modal">
<!-- more mapping info here -->
<class name="Company" table="[Company]">
<id name="Id" column="Id">
<generator class="native" />
</id>
<property name="Name" column="Name" />
<bag name="company_product" table="[Company_Product]" inverse="true" lazy="true">
<key column="CompanyID" />
<one-to-many class="Company_Product" />
</bag>
</class>
</hibernate-mapping>
Company_Product.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="BusinessObjectApp"
namespace="BusinessObjectApp.Modal">
<!-- more mapping info here -->
<class name="Company_Product" table="[Company_Product]">
<id name="Id" column="Id">
<generator class="native" />
</id>
<property name="SalesDate" column="SalesDate" />
<!-- Many to many -->
<many-to-one class="Company" name="Company" column="CompanyID" />
<many-to-one class="Product" name="Product" column="ProductID" />
</class>
</hibernate-mapping>
Product.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="BusinessObjectApp"
namespace="BusinessObjectApp.Modal">
<!-- more mapping info here -->
<class name="Product" table="[Product]">
<id name="Id" column="Id">
<generator class="native" />
</id>
<property name="Name" column="Name" />
<bag name="company_product" table="[Company_Product]" inverse="true" lazy="true">
<key column="ProductID" />
<one-to-many class="Company_Product" />
</bag>
</class>
</hibernate-mapping>
I able to retrieve all item if using the code below:
IList<Company_Product> test = session.QueryOver<Company_Product>()
.List<Company_Product>();
However the code below produce error. I know that is because Company_Product class does not have the properties. I want to map them to the corresponding object like the output above. Is it possible?
string query = "SELECT C.Name, P.Name, CP.SalesDate FROM [Company_Product] CP " +
"LEFT JOIN [Company] C ON CP.CompanyID = C.Id " +
"LEFT JOIN [Product] P ON CP.ProductID = P.Id";
var test = session.CreateSQLQuery(query)
.SetResultTransformer(Transformers.AliasToBean<Company_Product>())
.List<Company_Product>();
UPDATE:
Now I able to retrieve the whole object.
Company_Product company_product = null;
Company company = null;
Product product = null;
IList<Company_Product> test = session.QueryOver<Company_Product>()
.Left.JoinAlias(cp => cp.Company, () => company)
.Left.JoinAlias(cp => cp.Product, () => product)
.SelectList(list => list
.Select(cp => cp.Company).WithAlias(() => company_product.Company)
.Select(cp => cp.Product).WithAlias(() => company_product.Product)
.Select(cp => cp.SalesDate).WithAlias(() => company_product.SalesDate)
)
.TransformUsing(Transformers.AliasToBean<Company_Product>())
.List<Company_Product>();
But I cannot set the nested property.
Company_Product company_product = null;
Company company = null;
Product product = null;
IList<Company_Product> test = session.QueryOver<Company_Product>()
.Left.JoinAlias(cp => cp.Company, () => company)
.Left.JoinAlias(cp => cp.Product, () => product)
.SelectList(list => list
.Select(cp => cp.Company.Name).WithAlias(() => company_product.Company.Name)
.Select(cp => cp.Product.Name).WithAlias(() => company_product.Product.Name)
.Select(cp => cp.SalesDate).WithAlias(() => company_product.SalesDate)
)
.TransformUsing(Transformers.AliasToBean<Company_Product>())
.List<Company_Product>();
Can anyone help me? I am new to Nhibernate and sorry for my bad English.
You do not need to use a native SQL query to achieve this.
You can use QueryOver but will have to use projections.
session.QueryOver<Company_Product>()
.JoinQueryOver<Company>(cp => cp.Company, ()=>companyAlias)
.JoinQueryOver<Product>(cp => cp.Product, ()=>productAlias)
.SelectList( l => l.Select( cp => companyAlias.Name)
.Select( cp => productAlias.Name)
.Select( cp => cp.SalesDate));
I haven't tested it, and it's been more than over a year since I've used NHibernate, but it should point you in the right direction. :)
Also, why are you using a bag ? I think a set might be more appropriate.
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 two classes:
namespace fm.web
{
public class User
{
public static string default_username = "guest";
public static string default_password = "guest";
private UserType usertype;
public virtual int? Id { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual DateTime Datecreated { get; set; }
public virtual string Firstname { get; set; }
public virtual string Lastname { get; set; }
public virtual string Email { get; set; }
public virtual UserType Usertype
{
get { return usertype; }
set { usertype = value; }
}
}
}
namespace fm.web
{
public class UserType
{
public virtual int? Id { get; set; }
public virtual string Title { get; set; }
}
}
Here are the mapping files
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="fm.web"
assembly="fm.web">
<class name="User" table="[user]">
<id name="Id">
<column name="id" />
<generator class="native" />
</id>
<property name="Username" />
<property name="Password" />
<property name="Datecreated" />
<many-to-one name="Usertype"
class="UserType"
column="[type]"
cascade="all"
lazy="false"
/>
<property name="Firstname" />
<property name="Lastname" />
<property name="Email" />
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="fm.web"
assembly="fm.web">
<class name="UserType" table="[user_type]">
<id name="Id">
<column name="id" />
<generator class="native" />
</id>
<property name="Title" />
</class>
</hibernate-mapping>
I'm getting an exception: DuplicateMappingException
Could not compile the mapping document: fm.web.data.User.hbm.xml
Duplicate class/entity mapping User
Is nhibernate always this hard? Maybe I need a different framework.
I really think the mappings are fine which leads me to believe that the configuration setup is not quite right.
Please can you check that BuildSessionFactory is only called once on application start up.
Also please check that you are not including the mapping files twice as this will also throw this type of error.
Please post your configuration code.
You are correct in thinking that NHibernate is difficult to grasp for new comers espically the session management and mappings. Once you have grasped this then things get easier and are well worth the effort.
I'm starting to learn NHibernate (3.0) and picked up a copy of Packt Publishing's NHibernate 3.0 Cookbook.
There's a section on one-to-many mappings which I'm walking through but with my own database. It suggests I should do something like this to model a one to many relationship between customers and their domains:
public class Customer
{
public virtual int Id { get; protected set; }
public virtual string CustomerName { get; set; }
// Customer has many domains
public virtual IList<Domain> Domains { get; set; }
}
public class Domain
{
public virtual int Id { get; protected set; }
public virtual int CustomerID { get; set; }
public virtual string DomainName { get; set; }
}
Customer Mapping:
<class name="Customer" table="tblCustomer">
<id name="Id">
<column name="CustomerID" sql-type="int" not-null="true"/>
<generator class="identity"/>
</id>
<property name="CustomerName" column="Customer"/>
<list name="Domains">
<key column="CustomerID"/>
<one-to-many class="Domain" />
</list>
</class>
When I run this I get the following error:
XML validation error: The element 'list' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'one-to-many' in namespace 'urn:nhibernate-mapping-2.2'. List of possible elements expected: 'index, list-index' in namespace 'urn:nhibernate-mapping-2.2'.
The book's example is a bit more complex in that they use table-per-subclass mappings:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Eg.Core"
namespace="Eg.Core">
<subclass name="Movie" extends="Product">
<property name="Director" />
<list name="Actors" cascade="all-delete-orphan">
<key column="MovieId" />
<index column="ActorIndex" />
<one-to-many class="ActorRole"/> <-- Is this wrong?
</list>
</subclass>
</hibernate-mapping>
I'm guessing the book is wrong?
No, your mapping is missing the index element. A list in NHibernate is an ordered set, if you want an unordered set use bag mapping.
When i map columns from the inspected table, i do this:
<property name="InstanceName" type="MyNameUserType, MyApp.MyNamespace">
<column name="Name"/>
<column name="Name2"/>
</property>
How can I make property mapping initialize a UserType with data retrieved by the formula's sql query?
<property name="InstanceName" type="MyNameUserType, MyApp.MyNamespace" formula="(...)"/>
fails with an exception "wrong number of columns".
Thanks in advance!
MyUserNameType should be a class level mapping so that you can map the result of the SQL function to a class. See these two posts for some possible help:
Class and SQL Function example: http://thoughtspam.spaces.live.com/blog/cns!253515AE06513617!478.entry
NHibernate Mapping with formula mapping example:
http://thoughtspam.spaces.live.com/blog/cns!253515AE06513617!477.entry
I'm the author of the articles referenced by Michael. I had no idea people where still interested and I'm not sure it's applicable with the latest NHibernate.
Here's a fresh link though: http://thoughtspam.wordpress.com/2007/12/19/nhibernate-property-with-formula/
example, using Northwind...
Mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="PropertyFormulaExample.Shipper, PropertyFormulaExample" table="Shippers" lazy="false" >
<id name="ShipperID" column="ShipperID" unsaved-value="0">
<generator class="native" />
</id>
<property name="CompanyName" column="CompanyName" />
<property name="Phone" column="Phone" />
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="PropertyFormulaExample.Order, PropertyFormulaExample" table="Orders" lazy="false">
<id name="OrderID" column="OrderID" unsaved-value="0">
<generator class="native" />
</id>
<property name="CustomerID" column="CustomerID" />
<property name="ShipVia" type="PropertyFormulaExample.Shipper, PropertyFormulaExample" formula="dbo.GetShipper(shipvia)" />
</class>
</hibernate-mapping>
Entities:
public class Order
{
public int OrderID { get; set; }
public string CustomerID { get; set; }
public Shipper ShipVia { get; set; }
}
public class Shipper : ILifecycle
{
public int ShipperID { get; set; }
public string CompanyName { get; set; }
public string Phone { get; set; }
#region ILifecycle Members
public LifecycleVeto OnDelete(NHibernate.ISession s)
{
throw new NotImplementedException();
}
public void OnLoad(NHibernate.ISession s, object id)
{
}
public LifecycleVeto OnSave(NHibernate.ISession s)
{
throw new NotImplementedException();
}
public LifecycleVeto OnUpdate(NHibernate.ISession s)
{
throw new NotImplementedException();
}
#endregion
}
And finally the SQL function:
CREATE FUNCTION dbo.GetShipper(#shipperId int)
RETURNS int
AS
BEGIN
RETURN #shipperId
END
Obviously, you’ll want the function to do something meaningful, but the idea is you return the PK for the entity and implement ILifecycle.