I want map my object model to NHibernate. There is one tricky part in my concept and I don't know if it is possible to do this in NHibernate.
I want to have a collection of trees. I have two classes (below, only important properties indicated). Component is a node of a tree and ComponentGroup is a collection of trees.
public class Component
{
public Component Parent { get; set; }
public IList<Component> SubComponents { get; set; }
public ComponentGroup Group { get; set; }
}
public class ComponentGroup
{
public IList<Component> Components { get; set; }
}
Now I want each Component to know which ComponentGroup it belongs to, so I need reference from every Component to ComponentGroup (Group property). But ComponentGroup should have only collection of root nodes (direct children) - Components collection. So this is something like one-to-half mapping ;) "one" side has reference only to some items from "many" side.
Do you have any ideas how to map something like this using NHibernate?
I'll give it a shot (generated with FluentNHibernate)
<class name="Component" table="`Component`" xmlns="urn:nhibernate-mapping-2.2">
<id name="ComponentId" type="Int32" column="ComponentId">
<generator class="identity" />
</id>
<many-to-one name="Parent" column="ParentId" />
<bag name="SubComponents">
<key column="ComponentId" />
<one-to-many class="NHibernateTests.Component, NHibernateTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
<many-to-one name="Group" column="GroupId" />
<class name="ComponentGroup" table="`ComponentGroup`" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" type="Int32" column="ComponentGroupId">
<generator class="identity" />
</id>
<bag name="Components">
<key column="ComponentGroupId" />
<one-to-many class="NHibernateTests.Component, NHibernateTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
EDIT:
If you want all your Components to know their ComponentGroup then set on all of them the ComponentGroup .
And in ComponentGroup if you want all the root components only then change the bag to :
<bag name="Components" where="ParentId is null">
so you only get the root components
Related
I have 2 tables
Account
-Id
-AccountName
Contractor
-Id
-AccountId referneces account table
-Code
When I Insert record in Contractor table, it should insert the name of the Contractor in the Account table as Account Name and store the AccountId in the Contractor table.
Can somebody help me in generating the mapping file for this? I tried the following below
Account.hbm
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernateService" assembly="NHibernateService">
<class name="Account" table="tbl_Account" entity-name="Account">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="AcName" column="AcName" type="string" length="50" />
</class>
Contractor.hbm
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernateService" assembly="NHibernateService">
<class name="Contractor" table="tbl_Contractor" entity-name="Contractor">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="Code" column="Code" type="string" length="50"/>
<bag name="Account" cascade="none" lazy="false">
<key column="Id"/>
<one-to-many entity-name="Account" />
</bag>
</class>
</hibernate-mapping>
The Contractor mapping should be quiet different. The table/schema structure says, that each Contract can have exactly one Account...not more. So there cannot be <bag> but we need <many-to-one>: (see 5.1.10. many-to-one)
<class name="Contractor" table="tbl_Contractor" entity-name="Contractor">
<id name="id" column="id">
<generator class="native" />
</id>
<property name="Code" column="Code" type="string" length="50"/>
<!--<bag name="Account" cascade="none" lazy="false">
<key column="Id"/>
<one-to-many entity-name="Account" />
</bag>-->
<many-to-one name="Account" column="AccountId" cascade="all" >
</class>
The Contractor C# definition should be like
public class Contractor
{
public virtual Account Account { get; set; }
...
And that all together should work for us, because we instructed the NHibernate to cascade changes into second end:
var contract = new Contract {... };
var account = new Account { .. };
contract.Account = account;
session.Save(contract); // both are persisted
It's been a while since I used NHibernate, but I believe you need to configure the cascade for the child collection on parent object which in your case is the Contractor class.
That way, when you do this:
var contractor = new Contractor { Code = "XX" };
contractor.Account.Add(new Account { Name = "Account Name" });
session.Insert(contractor);
NHibernate will generate:
INSERT INTO Account ...
INSERT INTO Contractor ...
You currently have it set to none, you probably want either all or save-update depending on whether you want deletes to cascade too. Check out this page for further info.
I am new to NHibernate. Just started learning NHibernate.
I am getting for run-time error with my C# code
NHibernate.PropertyAccessException was unhandled
Message=Invalid Cast (check your mapping for property type mismatches); setter of NHibernateDemo.Customer
InnerException: System.InvalidCastException
Message=Unable to cast object of type 'NHibernate.Collection.Generic.PersistentGenericSet`1[NHibernateDemo.Order]' to type 'System.Collections.Generic.ISet`1[NHibernateDemo.Order]'.
Following is my C# code written for
public class Customer {
public Customer()
{
MemberSince = DateTime.UtcNow;
Orders = new HashSet<Order>();
}
public virtual Guid Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Location Address { get; set; }
public virtual ISet<Order> Orders { get; set; }
}
It's hbm file is as follow
<class name="Customer" table="Customer" lazy="true" >
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="FirstName" />
<property name="LastName" />
<component name="Address" >
<property name="Street" />
<property name="City" />
<property name="State" />
<property name="Country" />
</component>
<set name="Orders" table="`Order`" order-by="Ordered desc">
<key column="CustomerId" />
<one-to-many class="Order" />
</set>
</class>
public class Order {
public virtual Guid Id { get; set; }
public virtual DateTime Ordered { get; set; }
public virtual DateTime Shipped { get; set; }
public virtual Location ShipAddress { get; set; }
public virtual Customer Customer { get; set; }
}
<class name="Order" table="`Order`">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Ordered" />
<property name="Shipped" />
<component name="ShipAddress" >
<property name="Street" />
<property name="City" />
<property name="State" />
<property name="Country" />
</component>
<many-to-one name="Customer" column="CustomerId" />
</class>
If I change "set" section to "list" in Customer HBM file and do necessary changes in Customer class. Program is running correctly. Also if i remove "set" section from Customer HBM file; it is working.
Can you please help me to find what is wrong with "set" section of Customer HBM file?
Your mapping is almost correct, but the ISet interface is not from System namespace but from iesi library (distributed with NHibernate)
So you can reference iesi and change your mapping:
public virtual Iesi.Collections.Generic.ISet<Order> Orders { get; set; }
Or use the IList<>
and mapping with a bag
<bag name="Orders" table="`Order`" order-by="Ordered desc">
<key column="CustomerId" />
<one-to-many class="Order" />
</bag>
NOTE also do not forget to init the list, just in case that entity is created via new operator and NOT loaded by NHibernate
I have such a simple model:
public abstract class Entity
{
public virtual Guid Id { get; protected set; }
}
public class Post : Entity
{
public String Title { get ; set; }
public String Content { get; set; }
public DateTime Timestamp { get; set; }
public Byte[] Thumbnail { get; set; }
}
public class Blog : Entity
{
public String Title { get; set; }
public ISet<Post> Posts { get; set; }
}
Then I have such mappings:
BLOG
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true assembly="Application.Domain" namespace="Application.Domain.Entities">
<class name="Blog">
<!-- id generator -->
<id name="Id">
<generator class="guid.comb" />
</id>
<!-- properties/columns -->
<property name="Title" not-null="true" />
<!-- components/columns -->
<!-- associations -->
<set name="Posts" cascade="all">
<!-- key column? -->
</set>
</class>
</hibernate-mapping>
POST
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="Application.Domain" namespace="Application.Domain.Entities">
<class name="Blog">
<!-- id generator -->
<id name="Id">
<generator class="guid.comb" />
</id>
<!-- properties/columns -->
<property name="Title" not-null="true" />
<property name="Content" not-null="true" />
<property name="Timestamp" not-null="true"/>
<property name="Thmbnail" />
<!-- components/columns -->
<!-- associations -->
</class>
</hibernate-mapping>
How do I map one-to-many association (unidirectional)?
Thanks!
In the blog mapping file you need to define a one-to-many relation between the foreign key column that references the Blog entity in the post table say it is BlogId and you need to tell'em what class this one-to-many relation relates to, in your case this will be the Post class and you need to define it with it's fully qualified namespace that contains this class and a comma then the assembly name as following:
<set name="Posts" table="Post">
<key column="BlogId"/>
<one-to-many class="Application.Domain.Entities.Post, Application.Domain"/>
</set>
I think it's the same problem as described here.
And also, I think you have a mistake in mapping of the Post - class name shouldn't be Blog. Also, there's no relation from Post to Blog in your example.
I need to store my enums in the database as varchar instead of nvarchar, so I am using the "AnsiString" mapping as follows:
public class Document
{
public virtual int Id { get; set; }
public virtual string Content { get; set; }
public virtual DocType Type { get; set; }
}
public enum DocType
{
Word,
Excel
}
public class DocumentMap : ClassMap<Document>
{
public DocumentMap()
{
Id(d => d.Id);
Map(d => d.Content);
Map(d => d.Type).CustomType("AnsiString");
}
}
Saving to the database works fine, but when it comes to retrieval I receive an error:
NHibernate.PropertyAccessException: Invalid Cast (check your mapping for property type mismatches); setter of Core.Document
It works fine when I remove the CustomType("AnsiString") from the mapping.
Any suggestions?
Here's the hbm:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class xmlns="urn:nhibernate-mapping-2.2" name="Core.Document, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Document`">
<id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Id" />
<generator class="identity" />
</id>
<property name="Content" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Content" />
</property>
<property name="Type" type="AnsiString">
<column name="Type" />
</property>
</class>
</hibernate-mapping>
Essentially a duplicate of this question: How do you map an enum as string in fluent nhibernate?
The difference is that you're trying to use a Non-Unicode string (NVARCHAR in MSSQL) as the underlying data type.
So, what does the underlying mapping generated by FNH look like?
Using this did the trick (thanks #Firo):
Map(d => d.Type).CustomSqlType("varchar(50)");
Looking at the hbm (thanks #rbellamy for the suggestion) shows that using the custom sql type doesn't override the enum mapper from the mappings, so the resulting hbm looks like this:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class xmlns="urn:nhibernate-mapping-2.2" name="Core.Document, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Document`">
<id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Id" />
<generator class="identity" />
</id>
<property name="Content" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Content" />
</property>
<property name="Type" type="FluentNHibernate.Mapping.GenericEnumMapper`1[[Core.DocType, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], FluentNHibernate, Version=1.3.0.717, Culture=neutral, PublicKeyToken=8aa435e3cb308880">
<column name="Type" sql-type="varchar(50)" />
</property>
</class>
</hibernate-mapping>
and all works as it should.
I have problem using Fluent Nhibernate, I have following model. When I try to save Hotel with has new Geography I getting foreign key exception, looks like Nhibenate fails to save data in correct order, is it something I can correct via Fluent Nhibernate ?
public class Geography {
public virtual int CityID { get; set; }
public virtual string CountryCode { get; set; }
}
public class Hotel
{
public virtual int HotelID { get; set; }
public virtual Geography City { get; set; }
}
Mapping
public class HotelMap : ClassMap<Hotel>
{
public HotelMap()
{
Id(x => x.HotelID)
.GeneratedBy
.Identity();
References(x => x.City, "CityId")
.Cascade.All();
}
}
public class GeographyMap : ClassMap<Geography>
{
public GeographyMap()
{
Id(x => x.CityID);
Map(x => x.CountryCode);
HasMany(a => a.Hotels)
.Cascade.All();
}
}
Added generated mappings
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Hotel" table="`Hotel`">
<id name="HotelID" type="System.Int32">
<column name="HotelID" />
<generator class="assigned" />
</id>
<many-to-one cascade="all" class="Geography" foreign-key="HotelGeography" name="City">
<column name="CityId" />
</many-to-one>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Geography" table="`Geography`">
<id name="CityID" type="System.Int32">
<column name="CityID" />
<generator class="assigned" />
</id>
<bag cascade="all" inverse="true" name="Hotels" mutable="true">
<key>
<column name="HotelID" />
</key>
<one-to-many class="Hotel" />
</bag>
<property name="CountryCode" type="System.String">
<column name="CountryCode" />
</property>
</class>
</hibernate-mapping>
Try to specify identity generator for Geography class. If you want to have a database-generated unique identifier (like identity), you can accomplish that by adding .GeneratedBy.Native() to your Id(...) in GeographyMap class.
Add .Inverse() to the collection mapping.
For more details, have a look at http://nhibernate.info/doc/nhibernate-reference/collections.html#collections-onetomany and http://nhibernate.info/doc/nhibernate-reference/collections.html#collections-bidirectional