Mapping legacy database with possibly a discriminator - nhibernate

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.

Related

Is my NHibernate book wrong?

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.

Is it possible to use an NHibernate type discriminator as part of a foreign key

Taking the following object model:
public abstract class Entity
{
public Guid Id { get; set; }
}
public class Category : Entity
{
public string Name { get; set; }
public ICollection<LocalizedProperty> LocalizedProperties { get; set; }
}
public class Product : Entity
{
public string Name { get; set; }
public ICollection<LocalizedProperty> LocalizedProperties { get; set; }
}
public class LocalizedProperty : Entity
{
public string CultureName { get; set; }
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
}
Is it possible to use a type discriminator along with the entity's Id as the foreign key. The idea is that the resultant LocalizedProperties table would be:
LocalizedProperties
-------------------
Id
EntityType
EntityId
CultureName
PropertyName
PropertyValue
I know this is possible using Table-per-subclass mapping where each of my "Localized" entities inherit from a base localized entity class, which in turn has the association with LocalizedProperty. However, I would rather not have this extra level of inheritance if the above is possible.
Thanks,
Ben
UPDATE
Thanks to Diego for providing the solution using confORM. For those of you using traditional mapping files, I have converted the example from http://fabiomaulo.blogspot.com/2010/11/conform-any-to-many.html
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="ConfOrm.UsageExamples.CreateXmlMappingsInBinFolder" assembly="ConfOrm.UsageExamples" xmlns="urn:nhibernate-mapping-2.2">
<class name="Blog">
<id name="Id" type="Guid">
<generator class="guid.comb" />
</id>
<property name="Title" />
<property name="Subtitle" />
<set name="Tags" cascade="all" where="TagedItemClass = 'ConfOrm.UsageExamples.CreateXmlMappingsInBinFolder.Blog'">
<key column="TagedItemId" foreign-key="none" />
<one-to-many class="Tag" />
</set>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="ConfOrm.UsageExamples.CreateXmlMappingsInBinFolder" assembly="ConfOrm.UsageExamples" xmlns="urn:nhibernate-mapping-2.2">
<class name="Tag">
<id name="Id" type="Guid">
<generator class="guid.comb" />
</id>
<property name="Name" />
<any id-type="Guid" name="TagedItem">
<column name="TagedItemClass" />
<column name="TagedItemId" />
</any>
</class>
</hibernate-mapping>
You can use <any>.
http://nhibernate.info/doc/nh/en/index.html#mapping-types-anymapping
For a full example, check http://fabiomaulo.blogspot.com/2010/11/conform-any-to-many.html. I think it's exactly what you need.

How to query collections in NHibernate

I have a class:
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IDictionary<string, string> Attributes { get; set; }
}
and a mapping file:
<class name="User" table="Users">
<id name="Id">
<generator class="hilo"/>
</id>
<property name="Name"/>
<map name="Attributes" table="UserAttributes">
<key column="UserId"/>
<index column="AttributeName" type="System.String"/>
<element column="Attributevalue" type="System.String"/>
</map>
</class>
So now I can add many attributes and values to a User.
How can I query those attributes so I can get ie.
Get all the users where attributename is "Age" and attribute value is "20" ?
I don't want to do this in foreach because I may have millions of users each having its unique attributes.
Please help
You can do it using HQL.
For example:
from User u join u.Attributes attr
where index(attr) = 'Age' and attr = '20'

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.

NHibernate property formula filter

I have a following class:
MyClass
public virtual int Id { get; set; }
public virtual int Code { get; set; }
public virtual int Description { get; set; }
public virtual int Name { get; set; }
with the following mapping:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="TestApplication" assembly="TestApplication">
<class name="MyClass" table="MyTable">
<id name="Id" column="id">
<generator class="native"/>
</id>
<property name="Code" column="code"/>
<property name="Description" column="description"/>
<property name="Name" formula="(SELECT b.translation FROM translations b WHERE b.translation_id = translation_id AND b.language_id = :TranslationFilter.LanguageId)"/>
</class>
<filter-def name="TranslationFilter">
<filter-param name="LanguageId" type="Int32"/>
</filter-def>
</hibernate-mapping>
I'm trying to load entity through spring with:
Session.EnableFilter("TranslationFilter").SetParameter("LanguageId", 1);
return Session.Get<MyClass>(1);
but I'am getting adoexception. I see (in a profiler) that variable :TranslationFilter.LanguageId is not replaced with ? and that parameter value is not send to the server?
Is it this possible (to have filters in formula) and how?
Many thanks!
This feature is not officially supported. As such oren's blog post about this combination of 2 different features (formulas and filters) should be taken with a grain of salt...