I'm moving from xml mapping to a code based mapping. There is a problem I'm experiencing with NHibernate Map collection.
Below is the xml mapping which perfectly works (it is a bit simplified, there is actually more properties and collections):
<class name="Company" where="IsDeleted=0" lazy="false">
<id name="Id">
<generator class="guid"></generator>
</id>
<map name="Contacts" lazy="true" cascade="all" where="IsDeleted=0">
<key column="CompanyId"></key>
<index column="Id" type="guid"></index>
<one-to-many class="CompanyContact"/>
</map>
</class>
The alternate code mapping I came up with is next:
public CompanyMap()
{
Id(x => x.Id, mapper => mapper.Generator(Generators.Guid));
Map(x => x.Contacts,
m =>
{
m.Where(FILTER);
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.Lazy);
m.Key(c => c.Column("CompanyId"));
}, k =>
{
k.Element(e =>
{
e.Column("Id");
});
k.OneToMany(e => e.Class(typeof(CompanyContact)));
});
}
The above generates next hbml for map:
<map name="Contacts" lazy="true" cascade="all" where="IsDeleted=0">
<key column="CompanyId" />
<map-key type="Guid" />
<one-to-many class="CompanyContact" />
</map>
I'm obviously lacking index column here. Therefore when generating SQL nhibernate will use the DefaultIndexColumnName which is idx.
So the question is how would I set the index for map?
Update:
According to hibernate documentation I should be using map-key.
So to rephrase the question, how would I set the column property of map-key?
This is not yet implemented for NHibernate version 3.3.1.
Created an issue in Jira for that.
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.
While I am saving by calling SaveOrUpdate(), I got this warning and the data is not saving in the database after calling Transaction.Commit().
NHibernate.Engine.ForeignKeys - Unable
to determine if [project name] with
assigned identifier [primarykey] is
transient or detached; querying the
database. Use explicit Save() or
Update() in session to prevent this.
I am inserting a new object. Google search tell me to call Save() instead of SaveOrUpdate(): means Save() is only for inserting.
I search in Google and do not see much about this.
Could anyone give me suggestion for this problem or this warning?
Edit:
Here is the simulated sample mapping files -
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly=""
namespace="">
<class name="Customer" table="[dbo].[Customer]" optimistic-lock="none" >
<id name="CustomerId" column="CustomerId" >
<generator class="assigned"/>
</id>
<property name="Name" column="Name" />
<property name="Age" column="Age" />
<set name="CustomerDetails" cascade="none" inverse="true" fetch="select">
<key>
<column name="CustomerId"/>
</key>
<one-to-many class="CustomerDetail"/>
</set>
<many-to-one name="MGender" fetch="select" cascade="none">
<column name="GenderCode"/>
</many-to-one>
</class>
</hibernate-mapping>
<class name="CustomerDetails" table="[dbo].[CustomerDetail]" optimistic-lock="none" >
<id name="CustomerDetailId" column="CustomerDetailId" >
<generator class="assigned"/>
</id>
<property name="Detail1" column="Detail1" />
<many-to-one name="Customer" fetch="select" cascade="none">
<column name="CustomerId"/>
</many-to-one>
</class>
<class name="MGender" table="[dbo].[MGender]" optimistic-lock="none" >
<id name="GenderCode" column="GenderCode" >
<generator class="assigned"/>
</id>
<property name="Description" column="Description" />
<set name="Customers" cascade="none" inverse="true" fetch="select">
<key>
<column name="GenderCode"/>
</key>
<one-to-many class="Customer"/>
</set>
</class>
You're using an assigned identifier so you need to set the unsaved-value attribute so that NHibernate can determine if an entity should be inserted or updated. Or you can explicitly call Save for new entities.
<id name="CustomerId" column="CustomerId" unsaved-value="???" >
<generator class="assigned"/>
</id>
Please note that if you are using a custom id, ie not using Id(x => x.Id).GeneratedBy.Assigned() and you do not have any version or timestamp columns, there is no way NHibernate would know if thuis is a new row or an existing one. So, I fixed my problem by using GeneratedBy.UuidString(). Works like a charm. Almost put my code into production with this error message, fortunately my information was being saved.
How to represent next nhibernate xml in fluent-nhibernate?
<set name="Items" lazy="true" table="CATEGORY_ITEMS">
<key column="CATEGORY_ID"/>
<composite-element class="CategorizedItem">
<parent name="Category"/>
<many-to-one name="Item"
class="Item"
column="ITEM_ID"
not-null="true"/>
<property name="Username" column="USERNAME" not-null="true"/>
<property name="DateAdded" column="DATE_ADDED" not-null="true"/>
</composite-element>
</set>
HasMany(x => x.Items)
.Table("CATEGORY_ITEMS")
.Component(com =>
{
com.ParentReference(x => x.Category);
com.References(x => x.Item)
.Not.Nullable();
com.Map(x => x.Username)
.Not.Nullable();
com.Map(x => x.DateAdded)
.Not.Nullable();
});
I encourage you to look into conventions for specifying the repetitious parts of your mappings, like the uppercase column and table names.
i want to convert this mapping file from NHibernate to Fluent NHibernate
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="CustomCollectionsBasic.Core.Category, CustomCollectionsBasic.Core" table="Categories">
<id name="ID" column="CategoryID" unsaved-value="0">
<generator class="identity" />
</id>
<property name="Name" column="CategoryName" />
<bag name="ProductsInCategory" table="Products" cascade="all" inverse="true"
collection-type="CustomCollectionsBasic.Data.Collections.PersistentProductsType, CustomCollectionsBasic.Data">
<key column="CategoryID" />
<one-to-many class="CustomCollectionsBasic.Core.Product,ustomCollectionsBasic.Core" />
</bag>
</class>
</hibernate-mapping>
I try to convert it by NHibernateHbmToFluent and the result is
public class CategoryMap: ClassMap<CustomCollectionsBasic.Core.Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.ID)
.GeneratedBy.
.UnsavedValue(0);
Map(x => x.Name, "CategoryName");
HasMany<CustomCollectionsBasic.Core.Product>(x => x.ProductsInCategory)
.AsBag()
.KeyColumn("CategoryID")
.Table("Products")
.Inverse()
.Cascade;
}
}
but it not working.
Any ideas on how to map this?
Thanks in advance.
Id(x => x.ID)
.GeneratedBy
.Identity()
.UnsavedValue(0);
I think the other stuff is OK
What is fuentHibernate? Why is it used? What is the difference between Hibernate and Fluent Hibernate?
Fluent NHibernate offers an alternative to NHibernate's standard XML mapping files. Rather than writing XML documents (.hbm.xml files), Fluent NHibernate lets you write mappings in strongly typed C# code. This allows for easy refactoring, improved readability and more concise code.
Traditional HBM XML mapping
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="QuickStart" assembly="QuickStart">
<class name="Cat" table="Cat">
<id name="Id">
<generator class="identity" />
</id>
<property name="Name">
<column name="Name" length="16" not-null="true" />
</property>
<property name="Sex" />
<many-to-one name="Mate" />
<bag name="Kittens">
<key column="mother_id" />
<one-to-many class="Cat" />
</bag>
</class>
</hibernate-mapping>
Fluent NHibernate equivalent
public class CatMap : ClassMap<Cat>
{
public CatMap()
{
Id(x => x.Id);
Map(x => x.Name)
.Length(16)
.Not.Nullable();
Map(x => x.Sex);
References(x => x.Mate);
HasMany(x => x.Kittens);
}
}