Fluent NHibernate - joined subclass ForeignKey Name - nhibernate

I am looking at moving to Fluent NHibernate - the only issue I have encounter so far is that you can not specify a foreign key name on a joined sub class mapping.
Has anyone got a solution for this, or a workaround?
I found this post but the suggestion clearly was not added to the code.
I would like to avoid customising the code myself if possible.
Any help would be great...
Example:
public class Product
{
public string Name { get; set; }
}
public class Hammer : Product
{
public string Description { get; set; }
}
public class ProductMap : ClassMap<Product, long>
{
public ProductMap()
{
Polymorphism.Implicit();
Map(x => x.Name);
}
}
public class HammerMap : SubclassMap<Hammer>
{
public HammerMap()
{
Extends<Product>();
}
}
This generates something like:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="field.camelcase-underscore" auto-import="false" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" dynamic-insert="true" dynamic-update="true" mutable="true" polymorphism="implicit" optimistic-lock="version" name="Domain.Product, Domain" table="Product">
<id name="Id" type="System.Int64">
<column name="Id" />
<generator class="native">
<param name="sequence">ProductId</param>
</generator>
</id>
<property name="Name" type="System.String">
<column name="Name" />
</property>
<joined-subclass name="Domain.Hammer, Domain" table="Hammer">
<key>
<column name="Product_Id" />
</key>
<property name="Description" type="System.String">
<column name="Description" />
</property>
</joined-subclass>
</class>
</hibernate-mapping>
Note that there is no foreign key name specified in the mapping hbm file - as in:
<joined-subclass name="Domain.Hammer, Domain" table="Hammer">
<key column="Product_Id" foreign-key="FK_Hammer_Product"/>
</joined-subclass>

Try something like this
public class JoinedSubclassForeignKeyConvention : IJoinedSubclassConvention
{
public void Apply(IJoinedSubclassInstance instance)
{
instance.Key.ForeignKey(string.Format("FK_{0}_{1}",
instance.EntityType.Name, instance.Type.Name));
}
}

Related

Cascade Save - StaleObjectStateException: Row was updated or deleted by another transaction

Having an issue with updating the NHibernate version. Current version is 3.3.1.4000 and trying to update to 4.
After updating the unit test which does save with cascade fails with:
NHibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [NHibernateTests.TestMappings.ProductLine#cdcaf08d-4831-4882-84b8-14de91581d2e]
The mappings:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
<class name="Product" lazy="false" table="UserTest">
<id name="Id">
<generator class="guid"></generator>
</id>
<version name="Version" column="Version" unsaved-value="0"/>
<property name="Name" not-null="false"></property>
<property name="IsDeleted"></property>
<bag name="ProductLines" table="ProductLine" inverse="true" cascade="all" lazy="true" where="IsDeleted=0" >
<cache usage="nonstrict-read-write" />
<key column="UserId" />
<one-to-many class="ProductLine" />
</bag>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
<class name="ProductLine" where="IsDeleted=0" lazy="false">
<cache usage="nonstrict-read-write" />
<id name="Id">
<generator class="guid"></generator>
</id>
<version name="Version" column="Version" unsaved-value="0"/>
<property name="IsDeleted"></property>
<many-to-one name="Product" class="Product" column="UserId" not-null="true" lazy="proxy"></many-to-one>
</class>
</hibernate-mapping>
Classes:
public class Product
{
public Guid Id { get; set; }
public int Version { get; set; }
public bool IsDeleted { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public IList<ProductLine> ProductLines { get; private set; }
public Product()
{
ProductLines = new List<ProductLine>();
}
}
public class ProductLine
{
public Guid Id { get; set; }
public int Version { get; set; }
public bool IsDeleted { get; set; }
public Product Product { get; set; }
}
The test:
[TestMethod]
public void CascadeSaveTest()
{
var product = new Product
{
Id = Guid.NewGuid(),
Name = "aaa",
IsActive = true
};
var productLine = new ProductLine
{
Id = Guid.NewGuid(),
Product = product,
};
product.ProductLines.Add(productLine);
using (var connection = new RepositoryConnection())
{
using (var repositories = new Repository<Product>(connection))
{
repositories.Create(product);
//the below just calls the Session.Transaction.Commit();
connection.Commit(); //NH3.3.1.400 passes, NH4 fails
}
}
}
Thanks for you ideas in advance.
Well, I guess I have now understood what causes the error with NH 4. And If I am right, that is a bit contrived case causing this behavior to be hard to qualify as a bug.
In your example, products lines are mapped through a bag. A bag can contains duplicates, which requires an intermediate table between Product and ProductLine. (Something like a ProductProductLine table with an UserId (ProductId) column, and a ProductLineId column.)
You have set this intermediate table as being the ProductLine table. I suspect the commit to db causes NHibernate to insert the product, then the product line, then to try to insert the relationship by inserting again in ProductLine table. (You may check that by profiling SQL queries on your db.)
Things are a bit muddy, since doc states (emphasis is mine):
table (optional - defaults to property name) the name of the
collection table (not used for one-to-many associations)
But then, how to honor the bag semantics allowing duplicates in the collection? From the same doc:
A bag is an unordered, unindexed collection which may contain the same
element multiple times.
Anyway, within your example, you really should map your one-to-many with a set, as shown in doc.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
<class name="Product" lazy="false" table="UserTest">
<id name="Id">
<generator class="guid"></generator>
</id>
<version name="Version" column="Version" unsaved-value="0"/>
<property name="Name" not-null="false"></property>
<property name="IsDeleted"></property>
<set name="ProductLines" inverse="true" cascade="all" lazy="true" where="IsDeleted=0" >
<cache usage="nonstrict-read-write" />
<key column="UserId" />
<one-to-many class="ProductLine" />
</set>
</class>
</hibernate-mapping>
And change your collection type for using .Net fx 4 System.Collections.Generic.ISet<T>.
public ISet<ProductLine> ProductLines { get; private set; }
public Product()
{
ProductLines = new HashSet<ProductLine>();
}
If this causes your trouble to disappear, it would mean something has changed in bag handling in NH 4. But should we consider this change as being a bug? Not sure, since using a bag in this case does not look right for me.
Drilling it further down turned out that NHibernate4 has problems identifying whether it is a new entity or an already existent when concerned with Cascade.
With the scenario in question it was calling a SQL Update for the ProductLine, rather than Create.
It works fine with the below changes, however I'm quite puzzled with such a changes in between NHibernate versions.
Change to ProductLine Mapping
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
<class name="ProductLine" where="IsDeleted=0" lazy="false">
<cache usage="nonstrict-read-write" />
<!-- here comes the updated line -->
<id name="Id" type="guid" unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid"></generator>
</id>
<version name="Version" column="Version" unsaved-value="0"/>
<property name="IsDeleted"></property>
<many-to-one name="Product" class="Product" column="UserId" not-null="true" lazy="proxy"></many-to-one>
</class>
</hibernate-mapping>
Change to test method
[TestMethod]
public void CascadeSaveTest()
{
var product = new Product
{
Id = Guid.NewGuid(),
Name = "aaa",
IsActive = true
};
var productLine = new ProductLine
{
Id = Guid.Empty, //The updated line
Product = product,
};
product.ProductLines.Add(productLine);
using (var connection = new RepositoryConnection())
{
using (var repositories = new Repository<Product>(connection))
{
repositories.Create(product);
connection.Commit();
}
}
}

Nhibernate one-to-one mapping

Hello guys I've tried searching for a solution to this problem for a period of time. Couldn't find it.
I have two classes which I will simplify. My problem is that i want a unidirectional one-to-one mapping between Player and Clan. Now I saw examples which have foreign key in ther id. But I don't understand it. This mapping is not producing a column in my Clans table for ClanLeader... Am i missing something? Thank you all for help.
public class Clan{
private Int32 id;
public virtual Int32 Id
{
get { return id; }
set { id = value; }
}
private string name;
public virtual string Name
{
get { return name; }
set { name = value; }
}
private Player clanLeader;
public virtual Player ClanLeader
{
get { return clanLeader; }
set { clanLeader = value; }
}
}
Then we have mapping for Clan:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="NHibernateSQLite"
namespace="NHibernateSQLite" >
<class name="GamingOrganizerDomainModel.Clan, GamingOrganizerDomainModel" table="Clans" lazy="false">
<id name="id" access="field" column="Clan_ID" type="Int32">
<generator class="native"></generator>
</id>
<property name="Name" column="Clan_Name" unique-key="ClanNameConstraint" type="String"/>
<one-to-one name="ClanLeader" class="GamingOrganizerDomainModel.Player, GamingOrganizerDomainModel" />
</class>
</hibernate-mapping>
Next is the class Player:
public class Player{
private Int32 id;
public virtual Int32 Id
{
get { return id; }
set { id = value; }
}
private string nickname;
public virtual string Nickname
{
get { return name; }
set { name = value; }
}
}
And mapping for Player:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="NHibernateSQLite"
namespace="NHibernateSQLite" >
<class name="GamingOrganizerDomainModel.Player, GamingOrganizerDomainModel" table="Players" lazy="false">
<id name="id" column="Player_ID" access="field" type="Int32">
<generator class="native" />
</id>
<property name="nickname" access="field" column="Nickname"/>
</class>
</hibernate-mapping>
Unidirectional one to one relation should be mapped as "many-to-one" element. "one-to-one" is used for bidirectional one to one. See this post for more details. Howerver there are ConfORM mappings as well the article is crystal clear.
You need make only one change in your Clan mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="GamingOrganizerDomainModel"
namespace="GamingOrganizerDomainModel" >
<class name="Clan" table="Clans" lazy="false">
<id name="id" access="field" column="Clan_ID" type="Int32">
<generator class="native"></generator>
</id>
<property name="Name" column="Clan_Name" unique-key="ClanNameConstraint" type="String"/>
<many-to-one name="ClanLeader" class="Player" />
</class>
You do not need to write assembly qualified class name in the mapping. Assembly and namespace attributes of hibernate-mapping element specify default namespace and assembly where NH tries to find specific class.

Nhibernate get collection by ICriteria

colleagues. I've got a problem at getting my entity. Mapping:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Clients.Core"
namespace="Clients.Core.Domains">
<class name="Sales, Clients.Core" table='sales'>
<id name="Id" unsaved-value="0">
<column name="id" not-null="true"/>
<generator class="native"/>
</id>
<property name="Guid">
<column name="guid"/>
</property>
<set name="Accounts" table="sales_users" lazy="false">
<key column="sales_id" />
<element column="user_id" type="Int32" />
</set>
</class>
Domain:
public class Sales : BaseDomain
{
ICollection<int> accounts = new List<int>();
public virtual ICollection<int> Accounts
{
get { return accounts; }
set { accounts = value; }
}
public Sales() { }
}
I want get query such as
SELECT *
FROM sales s
INNER JOIN sales_users su on su.sales_id=s.id
WHERE su.user_id=:N
How can i do this through ICriterion object?
Thanks a lot.
This is what I think the answer should be:
public IEnumerable<Sales> GetSalesForUser(int userId)
{
return session.CreateCriteria<Sales>()
.CreateAlias("Accounts", "accounts")
.Add(Restrictions.Eq("accounts.UserId", "userId"))
.List<Sales>();
}
But I'm confused by your model. It appears that Accounts has a many-to-many relationship with Sales, but you haven't mapped it that way. I'm not sure how to filter an int collection (HashSet in this case). You could try:
public IEnumerable<Sales> GetSalesForUser(int userId)
{
return session.CreateCriteria<Sales>()
.Add(Restrictions.Eq("Accounts", userId))
.List<Sales>();
}
var sales = session.CreateCriteria(typeof(Sales))
.SetFetchMode("Accounts", FetchMode.Join)
.SetResultTransformer(Transformers.DistinctRootEntity)
.List<Sales>();

How do I override the automapping for a composite Id, one to many relationship with fluent nhibernate?

I'm polling mulitple systems (domains) for security info so I'm dealing domainUsers and their roles. I've got my entities setup as show below, but I'm having trouble setting up the domainUser.HasMany relationship in the AutoMapper override.
You'll notice that I don't have domainUser.DomainUserId and role.RoleId which make this much more simple (no compositeIds.) I've avoided those fields because I've already got a natural composite key and it will be populate when I pull this data from the downstream domain. If I add those artificial keys, I'll have to pre-fetch their values before I call session.Merge(domainUser). I'm trying to avoid doing that.
The entity objects are obvious (I hope) but here is what I've got.
public class DomainUser
{
public virtual int Domain_Id { get; set; }
public virtual string DomainUserLogin { get; set; }
public virtual string EmployeeId { get; set; }
// extra field removed for breviety
public DomainUser()
{
this.Roles = new List<DomainUserRole>();
}
public virtual void AddRole(DomainUserRole role)
{
role.DomainUser = this;
this.Roles.Add(role);
}
// overrides for equals and getHashCode
}
and
public class DomainUserRole
{
public virtual DomainUser DomainUser { get; set; }
public virtual string DataSegment { get; set; } // Some group of data a user has access to, like US or China
public virtual string RoleName { get; set; }
public virtual string RoleDescription { get; set; }
// extra field removed for breviety
// overrides for equals and getHashCode
}
My db schema is pretty simple.
alt text http://lh6.ggpht.com/_MV6QGBD11JE/S3iX2qcP_jI/AAAAAAAAEE0/PGIO07BlCSo/s800/Untitled.gif.jpg
I've got the IAutoMappingOverride classes started like this. But, I'm at a loss of how to setup the hasMany for roles. It keeps giving me
NHibernate.FKUnmatchingColumnsException:
Foreign key (FK20531BE4163641BB:tblDomainUserRoles [DomainUser]))
must have same number of columns as the referenced primary key
(tblDomainUsers [Domain_Id, DomainUserLogin]).
How do I setup that foreign key to use both those fields?
public class DomainUserMap : IAutoMappingOverride<DomainUser>
{
public void Override(AutoMapping<DomainUser> mapping)
{
mapping.CompositeId()
.KeyProperty(user => user.Domain_Id, "Domain_Id")
.KeyProperty(user => user.DomainUserLogin, "DomainUserLogin");
// I"ve tried this.
// mapping.HasMany(x => x.Roles)
// .KeyColumns.Add("Domain_Id")
// .KeyColumns.Add("DomainUserLogin");
// also tried this where I define this FK in DB with both fields.
// mapping.HasMany(x => x.Roles)
// .ForeignKeyConstraintName("FK_tblDomainUserRoles_tblDomainUsers")
}
}
public class DomainUserRoleMap : IAutoMappingOverride<DomainUserRole>
{
public void Override(AutoMapping<DomainUserRole> mapping)
{
mapping.CompositeId()
.KeyReference(role => role.DomainUser)
.KeyProperty(role => role.DataSegment)
.KeyProperty(role => role.RoleName);
}
}
I did endup hand editing the hbm files. It looks like the most recent build on fluent Nhibernate addresses this issue. Here is what my hbm file look like.
<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" name="AAA.Core.Entities.DomainUser, AAA.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`tblDomainUsers`">
<composite-id mapped="false" unsaved-value="undefined">
<key-property name="Domain_Id" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Domain_Id" />
</key-property>
<key-property name="DomainUserLogin" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="DomainUserLogin" />
</key-property>
</composite-id>
... properties hidden for breviety
<bag inverse="true" cascade="all-delete-orphan" lazy="false" name="Roles">
<key>
<column name="Domain_Id" />
<column name="DomainUserLogin" />
</key>
<one-to-many class="AAA.Core.Entities.DomainUserRole, AAA.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
</class>
</hibernate-mapping>
here is the file for roles.
<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" name="AAA.Core.Entities.DomainUserRole, AAA.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`tblDomainUserRoles`">
<composite-id mapped="false" unsaved-value="undefined">
<key-property name="DataSegment" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="DataSegment" />
</key-property>
<key-property name="RoleName" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="RoleName" />
</key-property>
<key-many-to-one name="DomainUser" class="AAA.Core.Entities.DomainUser, AAA.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<column name="Domain_Id" />
<column name="DomainUserLogin" />
</key-many-to-one>
</composite-id>
... properties hidden for breviety
</class>
</hibernate-mapping>

Doubly connected ordered tree mapping using NHibernate

We need to map simple class using NHibernate:
public class CatalogItem
{
private IList<CatalogItem> children = new List<CatalogItem>();
public Guid Id { get; set; }
public string Name { get; set; }
public CatalogItem Parent { get; set; }
public IList<CatalogItem> Children
{
get { return children; }
}
public bool IsRoot { get { return Parent == null; } }
public bool IsLeaf { get { return Children.Count == 0; } }
}
There are a batch of tutorials in the internet on this subject, but none of them cover little nasty detail: we need order to be preserved in Children collection. We've tried following mapping, but it led to strange exeptions thrown by NHibernate ("Non-static method requires a target.").
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Domain.Model" assembly="Domain">
<class name="CatalogItem" lazy="false">
<id name="Id" type="guid">
<generator class="guid" />
</id>
<property name="Name" />
<many-to-one name="Parent" class="CatalogItem" lazy="false" />
<list name="Children" cascade="all">
<key property-ref="Parent"/>
<index column="weight" type="Int32" />
<one-to-many not-found="exception" class="CatalogItem"/>
</list>
</class>
</hibernate-mapping>
Does anyone have any thoughts?
I'm no expert, but <key property-ref=...> looks strange to me in this usage. You should be able to do <key column="ParentID"/>, and NHibernate will automatically use the primary key of the associated class -- itself, in this case.
You may also need to set the list to inverse="true", since the relationship is bidirectional. [See section 6.8 in the docs.]