Many-To-Many relationships with two natural keys - nhibernate

I have the following mapping
UserProfile.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="AngusBook.Domain"
namespace="AngusBook.Domain">
<class name="UserProfile">
<id name="UserId" type="int">
<generator class="identity" />
</id>
<natural-id mutable="false">
<property name="UserName" />
</natural-id>
<set name="Companies" table="Users_Companies">
<key column="UserId"/>
<many-to-many column="CompanyId" class="Company" />
</set>
</class>
</hibernate-mapping>
Company.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="AngusBook.Domain" namespace="AngusBook.Domain" >
<class name="Company">
<id name="Id">
<generator class="hilo" />
</id>
<natural-id mutable="false">
<property name="CompanyName" />
</natural-id>
<set name="Users" table="Users_Companies">
<key column="CompanyId"/>
<many-to-many column="UserId" class="UserProfile" />
</set>
</class>
</hibernate-mapping>
Table design
With this mapping I have, I can have two identical rows in Users_Companies table (ie: two rows with the same pair of foreign keys that belong to the UserProfile and Company table). Using mappings, how do I cause NHibernate or SQL to throw an error/exception when an attempt is made to insert a pair of foreign keys into Users_Companies that already exist in the table? I would like each row in Users_Companies to be unique and not have repetitive data.

I found out the problem. I didn't implement the Equals() and GetHashCode() correctly.
I ended up refactored my entities to use the following base entity class and that fixed the issue. No error is thrown when a duplicated entity is added to the set but the set will simply not add the duplicated entity.
Entity.cs
public abstract class Entity
{
public virtual TId Id { get; protected set; }
protected virtual int Version { get; set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity<TId>);
}
private static bool IsTransient(Entity<TId> obj)
{
return obj != null &&
Equals(obj.Id, default(TId));
}
private Type GetUnproxiedType()
{
return GetType();
}
public virtual bool Equals(Entity<TId> other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (!IsTransient(this) &&
!IsTransient(other) &&
Equals(Id, other.Id))
{
var otherType = other.GetUnproxiedType();
var thisType = GetUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
if (Equals(Id, default(TId)))
return base.GetHashCode();
return Id.GetHashCode();
}
}
public abstract class Entity : Entity<Guid>
{
}
UserProfile.cs
public class UserProfile : Entity<int>
{
public UserProfile()
{
Companies = new HashedSet<Company>();
}
public virtual string UserName { set; get; }
public virtual ISet<Company> Companies { set; get; }
}
Company.cs
public class Company : Entity<int>
{
public Company()
{
Users = new HashedSet<UserProfile>();
}
public Company(string name) :this()
{
this.CompanyName = name;
}
public virtual string CompanyName { set; get; }
public virtual ISet<UserProfile> Users { set; get; }
}

Related

Loading a collection of union-subclass entities polymorphically - column specified multiple times

Take the following entities:
public class Company : Entity<Guid>
{
public virtual string Name { get; set; }
public virtual IList<IEmployee> Employees { get; set; }
public Company()
{
Id = Guid.NewGuid();
Employees = new List<IEmployee>();
}
}
public interface IEmployee
{
Guid? Id { get; set; }
string Name { get; set; }
void Work();
Company Company { get; set; }
}
public class ProductionEmployee : Entity<Guid>, IEmployee
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
public ProductionEmployee()
{
Id = Guid.NewGuid();
}
public virtual void Work()
{
Console.WriteLine("I'm making the stuff.");
}
}
public class SalesEmployee : Entity<Guid>, IEmployee
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
public SalesEmployee()
{
Id = Guid.NewGuid();
}
public virtual void Work()
{
Console.WriteLine("I'm selling the stuff.");
}
}
Mapped in the following way in NHibernate:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PolymorphicUnionSubclass.Domain.Entities"
assembly="PolymorphicUnionSubclass.Domain">
<class name="Company" table="`Company`">
<id name="Id" column="Id" type="guid">
<generator class="assigned"/>
</id>
<property name="Name" column="`Name`"/>
<bag name="Employees" inverse="true" cascade="save-update">
<key column="CompanyId"></key>
<one-to-many class="IEmployee" />
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PolymorphicUnionSubclass.Domain.Entities"
assembly="PolymorphicUnionSubclass.Domain">
<class name="IEmployee" abstract="true">
<id name="Id" column="Id" type="guid">
<generator class="assigned"/>
</id>
<many-to-one name="Company" column="`CompanyId`" cascade="save-update"/>
<union-subclass name="ProductionEmployee" table ="`ProductionEmployee`" >
</union-subclass>
<union-subclass name="SalesEmployee" table ="`SalesEmployee`">
</union-subclass>
</class>
</hibernate-mapping>
If I create a Company entity and add IEmployee entities to its collection (also setting the Company property of the IEmployee entity to create the bi-directional relationship), Then when I save the company, everything goes into the database as expected. The companyId is set correctly on the PoductionEmployee and SalesEmlpoyee records.
But when I come to load it, I get the following error:
The column 'CompanyId' was specified multiple times for 'employees0_'
The generated SQL looks like this:
SELECT employees0_.CompanyId as CompanyId1_, employees0_.Id as Id1_, employees0_.Id as Id9_0_, employees0_.[CompanyId] as CompanyId2_9_0_, employees0_.clazz_ as clazz_0_
FROM ( select Id, CompanyId, CompanyId, 1 as clazz_ from [ProductionEmployee] union all select Id, CompanyId, CompanyId, 2 as clazz_ from [SalesEmployee] ) employees0_
WHERE employees0_.CompanyId=?
Why is it generating the CopmanyId column twice and how do I prevent this?
Nothing to do with union-subclass in the end. The problem was in the one-to-many collection I had specified column="CompanyId", and in the many-to-one I had specified column="`CompanyId`". Including the backticks in one and not the other had caused NHibernate to think they were different column. Never come across this in all my time using NHibernate.

Fluent NHibernate - mixing table-per-subclass and table-per-class-hierarchy

Give the following structure,
MyBaseClass {
public int Id {get; private set;}
}
MySubclassWithDiscriminator : MyBaseClass {
}
MySubclass : MyBaseClass {
public string SomeThing {get; set;}
}
How would I use Fluent NH to map these correctly, using a combination of table-per-subclass and table-per-class-hierarchy? I've tried a custom AutomappingConfiguration, but seem to be going around in circles:
public class AutomappingConfiguration : DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
return type.Namespace.Contains("Entities");
}
public override bool IsDiscriminated(Type type)
{
// only classes with additional properties should be
// using the table-per-subclass strategy
if ((type.IsAssignableFrom(typeof(MyBaseClass)) ||
type.IsSubclassOf(typeof(MyBaseClass)) &&
type.GetProperties(BindingFlags.Public |
BindingFlags.FlattenHierarchy)
.Count() <= 1))
{
return true;
}
return false;
}
}
public class SubclassConvention : ISubclassConvention
{
public void Apply(ISubclassInstance instance)
{
// Use the short name of the type, not the full name
instance.DiscriminatorValue(instance.EntityType.Name);
}
}
It seems to me from my investigation that the use of the Discriminator is a binary choice when using FNH, while an HBM has the ability to have a Discriminator column and a subclass at the same time.
EDIT - 2011-05-12
I rewrote this post to try to address James Gregory's comments.
Here's the HBM that I'm trying to achieve:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class
xmlns="urn:nhibernate-mapping-2.2"
name="Mixed_Parent"
abstract="true"
table="`Mixed_Parent`">
<id name="Id" type="System.Int32">
<generator class="identity" />
</id>
<discriminator type="String">
<column name="discriminator" />
</discriminator>
<subclass
name="Mixed_TPCH_Child"
discriminator-value="Mixed_TPCH_Child" />
<subclass
name="Mixed_TPS_Child"
discriminator-value="Mixed_TPS_Child">
<join table="`Mixed_TPS_Child`" >
<key column="Id" />
<property name="Description" type="String">
<column name="Description" />
</property>
</join>
</subclass>
</class>
</hibernate-mapping>
So, what I've seen is that the HBM that's generated is EITHER a <joined-subclass> or <subclass> without the <join> sub-element, never a combination of the two. Am I missing something here?
Here's a failing test that can be added to the SubclassPersistenceModelTests to illustrate:
namespace MixedTablePerSubclassWithTablePerClassHierarchy
{
public class Mixed_Parent
{
public virtual int Id { get; set; }
}
public class Mixed_TPCH_Child
{
}
public class Mixed_TPS_Child
{
public virtual string Description { get; set; }
}
public class Mixed_ParentMap : ClassMap<Mixed_Parent>
{
public Mixed_ParentMap()
{
Id(x => x.Id);
DiscriminateSubClassesOnColumn("discriminator");
}
}
public class Mixed_TPCH_ChildMap : SubclassMap<Mixed_TPCH_Child>
{ }
public class Mixed_TPS_ChildMap : SubclassMap<Mixed_TPS_Child>
{
public Mixed_TPS_ChildMap()
{
Map(x => x.Description);
}
}
}
[Test]
public void ShouldAllowMixedTablePerSubclassWithTablePerClassHierarchy()
{
var model = new PersistenceModel();
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_ParentMap());
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPCH_ChildMap()
);
model.Add(
new MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPS_ChildMap());
var classMapping = model.BuildMappings()
.First()
.Classes.First();
// WHAT SHOULD THIS NUMBER BE (0, 1 or 2)?
classMapping.Subclasses.Count().ShouldEqual(1);
classMapping
.Subclasses
.First()
.Type
.ShouldEqual(
typeof(
MixedTablePerSubclassWithTablePerClassHierarchy
.Mixed_TPS_Child)
); // WHICH OF THE CHILDREN WOULD BE FIRST?
}

NHibernate mapping does not populate the bag

<class name="CashThreshold" table="CASH_THRESHOLD_COUNTERS" lazy="true" >
<id name="Id" column="ID" >
<generator class="assigned" />
</id>
<bag name="ThresholdNominalsList" cascade="all" inverse="true" lazy="false" table="CASH_THRESHOLD_CAS_COUNTERS">
<key column="CASH_THRESHOLD_ID" />
<one-to-many class="NominalThreshold" />
</bag>
Map second table
<class name="NominalThreshold" table="CASH_THRESHOLD_CAS_COUNTERS" lazy="true" >
<composite-id>
<key-property name="CashTrasholdId" column="CASH_THRESHOLD_ID" type="long"></key-property>
<key-property name="Nominal" column="NOMINAL" type="long"></key-property>
</composite-id>
<property name="MinNoteCount" column="MIN_NOTE_COUNT" />
<property name="MaxNoteCount" column="MAX_NOTE_COUNT" />
Table classes
public class CashThreshold : ICashThreshold
{
public virtual long Id { set; get; }
/// !!!!!!! IS ALWAYS AMPTY, but not null !!!!!
public virtual IList<INominalThreshold> ThresholdNominalsList { set; get; }
}
public class NominalThreshold : INominalThreshold
{
public virtual long CashTrasholdId { set; get; }
public virtual long Nominal { set; get; }
public virtual long MinNoteCount { set; get; }
public virtual long MaxNoteCount { set; get; }
public override bool Equals(Object obj)
{
var tmp = (INominalThreshold)obj;
return (tmp.CashTrasholdId == CashTrasholdId && tmp.Nominal == Nominal);
}
public override int GetHashCode()
{
return (int)CashTrasholdId ^ (int)Nominal;
}
}
Function for getting list of ICashThreshold
ICriteria selectAll = currentSession.CreateCriteria<ICashThreshold>();
IList<ICashThreshold> list = selectAll.List<ICashThreshold>();
Query executed whith no errors. Bag-query executed successfully in sql-client and returned 4 result, but IList< INominalThreshold > ThresholdNominalsList has no elements.
Thanks.
Problem solved. NHibernate mapped bag successfully, but list was empty, because the data in DB was NOT COMMITTED. I inserted test data in the table, but did not commit it. When I execute query in sql-client, it executed successfully(because do it in session, where table rows inserted), but hibernate had another session. Thats why NHibernate could not see the table data.

Nhibernate mapping

I am trying to map Users to each other. The senario is that users can have buddies, so it links to itself
I was thinking of this
public class User
{
public virtual Guid Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string Password { get; set; }
public virtual DateTime? DateCreated { get; set; }
**public virtual IList<User> Friends { get; set; }**
public virtual bool Deleted { get; set; }
}
But am strugling to do the xml mapping.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyVerse.Domain"
namespace="MyVerse.Domain" >
<class name="User" table="[User]">
<id name="Id">
<generator class="guid" />
</id>
<property name="FirstName" />
<property name="LastName" />
<property name="EmailAddress" />
<property name="Password" />
<property name="DateCreated" />
<property name="Deleted" />
<set name="Friends" table="UserFriend">
<key foreign-key="Id"></key>
<many-to-many class="User"></many-to-many>
</set>
</class>
</hibernate-mapping>
something like
<bag name="Friends" table="assoc_user_table" inverse="true" lazy="true" cascade="all">
<key column="friend_id" />
<many-to-many class="User,user_table" column="user_id" />
</bag>
Consider using the repository pattern. Create a Repository contract and a base abstract class that takes one of your entities as a type (your mapped class)
Open the session when the repository is initialized and close when destroyed. (implement IDisposable).
Then make sure all of your access to the session happens within the using statement:
[pseudo-code]:
using(var repository = RepositoryFactory<EntityType>.CreateRepository())
{
var entity = repository.get(EntityID);
foreach (somesubclass in entity.subclasscollection)
{
//Lazy loading can happen here, session is still open with the repository
... Do Something
}
}
I use a base abstract class for my Repositories. This one is for my readonly repository but you'll get the drift. They key is to keep your units of work small, open the session only when you have something to do with the database, then let it close on the dispose. Here's the base class, disclaimer YMMV:
public interface IEntity
{
int Id { get; set; }
}
public interface IRORepository<TEntity> : IDisposable where TEntity : IEntity
{
List<TEntity> GetAll();
TEntity Get(int id);
}
public abstract class RORepositoryBase<T> : IRORepository<T> where T : IEntity
{
protected ISession NHibernateSession;
protected RORepositoryBase()
{
NHibernateSession = HibernateFactory.OpenSession();
NHibernateSession.DefaultReadOnly = true;
}
public ISession Session { get { return NHibernateSession; } }
public void Dispose()
{
NHibernateSession.Flush();
NHibernateSession.Close();
NHibernateSession.Dispose();
}
public virtual List<T> GetAll()
{
return NHibernateSession.Query<T>().ToList();
}
public virtual T Get(int id)
{
return NHibernateSession.Get<T>(id);
}
}

NHibernate property mapping: columns and formula

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.