NHibernate Stored procedure mapping properties on complex object - nhibernate

I have a SQL stored procedure which Im trying to use to load a collection of entities. My entities look like this
public class Person
{
public virtual int Id {get;set;}
public virtual string Name {get;set;}
public virtual Colour FavoriteColour {get;set;}
}
public class Colour
{
public virtual int Id {get;set;}
public virtual string Name {get;set;}
}
My stored procedure looks more or less like this:
Create Procedure getAllPersons
AS
SELECT
p.Id as PersonId,
p.Name as PersonName,
c.Id as ColourId,
c.Name as ColourName
FROM
Person p JOIN Colour c
p.ColourId = c.Id
My mapping looks like this
<class name="Person">
<id name="Id">
<generator class="native"/>
</id>
<property name="Name" />
<many-to-one name="FavoriteColour" class="Foo, Colour" column="ColourId"/>
</class>
<sql-query name="getAllPersons">
<return class="Person">
<return-property name="Id" column="PersonId"/>
<return-property name="Name" column="PersonName"/>
<return-property name="FavoriteColour">
<return-column name="ColourId"/>
</return-property>
</return>
exec getAllPersons
</sql-query>
I want to know how I can map the colourName column that comes our of my proc to the FavoriteColour.Name property on my Person entity so that
the FavoriteColour object on Person is populated with both its Id and Name properties. Any ideas?

Related

nHibernate named query, result transformation and column name with whitespace

I've got a named nHibernate query which returns custom data. So I decided to make a bean class to encapsulate data. Here is some code:
public IList<Report> GetReport(int reportId)
{
return Session.GetNamedQuery("GetReport")
.SetParameter("Id", reportId)
.List<Report>();
}
public class Report
{
public virtual string Id { get; set; }
...
public virtual string CustomColumn { get; set; }
}
and mapping:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="MyAssembly"
namespace="MyAssembly.Model">
<class name="Report" table="Report">
<id name="Id" column="Id">
<generator class="assigned"/>
</id>
...
<property column="`Custom column`" name="CustomColumn" />
</class>
<sql-query name="GetReport">
<return class="Report"/>
<query-param name="Id" type="int" />
exec GetReport :Id
</sql-query>
</hibernate-mapping>
But when I invoke this method I got an exception:
NHibernate.Exceptions.GenericADOException : could not execute query
----> System.IndexOutOfRangeException : [Custom column]
Help, anyone?
Ok, I found the problem myself. I thought, that I need to escape column names which contains spaces with ` as I do for table names. But apparently I shouldn't.
This syntax is fine.
<property column="Custom column" name="CustomColumn" />

Querying a many-to-many collection or how to include a many-to-many table in a criteria query?

I am quite new to the world of NHibernate and I can't seem to get this to work with the use of a criteria query: query a many-to-many relationship or query a collection (set/bag) on an entity. I've searched the internet and checked all the NHibernate books we have, but I can't find a specific answer to my "challenge".
I have made a simplified example of the problem I'm trying to solve. I have a table with books, a table with categories and a many-to-many table with the categories per book. Here are some of the technicalities:
data structure:
create table tableBook
(
BkId integer not null default autoincrement,
BkTitle char(40) not null,
BkWriter char(40) not null,
primary key (BkId)
);
create table tableCategory
(
CatId integer not null default autoincrement,
CatCode char(3) not null,
CatDesc char(40),
primary key (CatId)
);
create table tableCategoriesPerBook
(
CpbId integer not null default autoincrement,
CpbBkId integer not null, /*foreign key to tableBook*/
CpbCatId integer not null, /*foreign key to tableCategory*/
primary key (CpbId)
);
alter table tableCategoriesPerBook add foreign key FK_CpbBkId (CpbBkId) references tableBook (BkId) on update Restrict on delete Cascade;
alter table tableCategoriesPerBook add foreign key FK_CpbCatId (CpbCatId) references tableCategory (CatId) on update Restrict on delete Cascade;
create unique index idx_CpbCatId_CpbBkId on tableCategoriesPerBook (CpbCatId, CpbBkId);
C# classes:
public class BookEntity
{
public virtual Int32 BookId { get; set; }
public virtual string BookTitle { get; set; }
public virtual string BookWriter { get; set; }
private readonly IEnumerable<CategoryEntity> _categories = new ObservableCollection<CategoryEntity>();
public virtual IEnumerable<CategoryEntity> Categories
{
get { return _categories; }
}
}
public class CategoryEntity
{
public virtual Int32 CategoryId { get; set; }
public virtual string CategoryCode { get; set; }
public virtual string CategoryDesc { get; set; }
}
NHibernate mappings:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Domain" assembly="Domain" xmlns="urn:nhibernate-mapping-2.2">
<class name="Domain.BookEntity" table="tableBook">
<id name="BookId" column="BkId" type="Int32">
<generator class="native" />
</id>
<property name="BookTitle" column="BkTitle" type="string" length="40"/>
<property name="BookWriter" column="BkWriter" type="string" length="40"/>
<idbag name="_categories" access="field" table="tableCategoriesPerBook">
<collection-id type="Int32" column="CpbId">
<generator class="native"/>
</collection-id>
<key column="CpbBkId" property-ref="BkId"/>
<many-to-many column="CpbCatId" class="Domain.CategoryEntity, Domain" />
</idbag>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping namespace="Domain" assembly="Domain" xmlns="urn:nhibernate-mapping-2.2">
<class name="Domain.CategoryEntity" table="tableCategory">
<id name="CategoryId" column="CatId" type="Int32">
<generator class="native" />
</id>
<property name="CategoryCode" column="CatCode" type="string" length="3" />
<property name="CategoryDesc" column="CatDesc" type="string" length="40" />
</class>
</hibernate-mapping>
My question: is it possible to query (using ICriteria and/or detached criterias) the database in such a way that I get the books which is in one of the categories I specify (for instance: in catA or catB, could be "and" as well)? I want to optimize this in the query, not in C# (as I need to read all books from the database before I can filter the objects based on their collection of tags). If I'd write the SQL by hand, I would produce something like this:
SELECT * FROM tableBook
WHERE EXISTS
(
SELECT 1
FROM tableCategoriesPerBook
INNER JOIN tableCategory on (CpbCatId = CatId and CpbBkId = BkId)
WHERE CatCode in ('001', '002')
)
Since I don't have an entity for tableCategoriesPerBook, I don't see a way to get to this table with a criteria query. And I'd rather not add some handwritten piece of SQL expressions using:
criteria.Add(Expression.Sql("exists(.....)");
One last important factor: I am using a brownfield database, so I can't change the structure! this is what I'll have to work with database-wise.
This is pretty straight forward. You can use a detached criteria.
DetachedCriteria bookCategoryCriteria = DetachedCriteria.For<BookEntity>("bookCat");
bookCategoryCriteria
.CreateAlias("Categories", "cat", JointType.LeftOuterJoin)
.Add(Restrictions.In("cat.CategoryCode", categories)
.Add(Restrictions.Eq("bookCat.BookId", "book.BookId")
.SetProjection(Projections.Id());
Session.CreateCriteria<BookEntity>("book")
.Add(Subqueries.Exists(bookCategoryCriteria));

NHibernate - mapping connection-table & back-references

Hey,
I have to map the following entities:
class Document
{
public int DocumentId { get; set; }
public DocumentList ContainingList { get; set; }
}
class DocumentList
{
public int DocumentListId { get; set; }
public DateTime LastUpdateTime { get; set; }
public IList<Doucment> Documents { get; set; }
}
With the constraint that only one DocumentList can own a specific document (altough a collection-table exists here).
Mapping has to rely on the following tables (which cannot be changed, for the sake of simplicity):
TB_DOC
------
DOC_ID (int, PK)
DOC_CONTENT (blob)
TB_DOC_LIST
-----------
DOC_LIST_ID (int, PK)
DOC_LIST_UPDATE_TIME (datetime)
TB_LIST_AND_DOCS
----------------
DOC_LIST_ID
DOC_ID
So the mapping i tohught of would be like this:
enter code here
<class name="DocumentList" table="TB_DOC_LIST">
<id name="DocumentListId">
<column name="DOC_LIST_ID"/>
<generator class="assigned" />
</id>
<property name="LastUpdateTime" column="DOC_LIST_UPDATE_TIME ">
<set name="Documents" table="TB_LIST_AND_DOCS">
<key column="DOC_ID"></key>
<one-to-many class="Document" />
</set>
</class>
and:
<class name="Document" table="TB_DOC">
<id name="DocumentId">
<column name="DOC_ID"/>
<generator class="assigned" />
</id>
[ ??? ] - property to reference the "owner" document list
</class>
Now, following the known patterns, i can't figure out how should the back-link from Document to the DocumentList be mapped, since i have a "weired| one-to-many relation here, broken by a third table.
I also don't want a Document object to reference an IList to solve this with back-referencing many-to-many, since each Document has only one such "owner" DocumentList.
Any elegant idea? what am i mispercepting here?
cant test it right now, but a join could be used to get the reference id.
<class name="Document" table="TB_DOC">
<id name="DocumentId">
<column name="DOC_ID"/>
<generator class="assigned" />
</id>
<join table="TB_LIST_AND_DOCS">
<key column="DOC_ID"/>
<many-to-one class="DocumentList">
<column name="DOC_LIST_ID" />
</many-to-one>
</join>
</class>

NHibernate filter collection

Using NHibernate I want to filter a collection in a class to contain ONLY a subset of possible objects. Below I am including a sample table data to help explain. I can find no way to do this using NHibernate.
Table:DataObject
DataObjectId(PK) / Name / CurrentVersion
11 "data.txt" 2
12 "info.txt" 3
Table:DataObjectVersion
Id / Comment / VersionNumber / DataObjectId(FK)
31 "Genesis" 1 11 <= Ignore this object
32 "Changed data" 2 11 <= Get this object
34 "Genesis" 1 12 <= Ignore this object
35 "Changed info" 2 12 <= Ignore this object
36 "Added info" 3 12 <= Get this object
I want to join on a non-foreign key DataObject.CurrentVersion = DataObjectVersion.VersionNumber for each DataObject in one command.
Here are the classes and mapping files:
public class DataObject
{
public virtual int DataObjectId { get; set; }
public virtual string Name { get; set; }
public virtual int CurrentVersionNumber { get; set; }
public virtual IList<DataObjectVersion> Versions { get; set; }
}
<class name="DataObject" table="DataObject" lazy="false">
<id name="DataObjectId" column="DataObjectId" type="int">
<generator class="assigned" />
</id>
<property name="Name" column="Name" type="String(512)" />
<property name="CurrentVersionNumber" column="CurrentVersionNumber" type="int" />
<bag name="Versions" cascade="all-delete-orphan" inverse="true" lazy="false" >
<key column="DataObjectId" />
<one-to-many class="DataObjectVersion" />
</bag>
</class>
public class DataObjectVersion
{
public virtual int DataObjectVersionId { get; set; }
public virtual string Comment { get; set; }
public virtual int VersionNumber { get; set; }
public virtual int DataObjectId { get; set; }
}
<class name="DataObjectVersion" table="DataObjectVersion" lazy="false">
<id name="Id" column="DataObjectVersionId" type="int">
<generator class="assigned" />
</id>
<property name="Comment" column="Comment" type="String(512)" />
<property name="VersionNumber" column="VersionNumber" type="int" />
<property name="DataObjectId" column="DataObjectId" type="int" />
</class>
if you want to filter the collection on demand, using a filter is a valid choice.
You would need to declare the filter on both the Version class and in the bag element and apply the filter from the NHibernateSession.EnableFilter method
if you always want to fetch a single Version in the bag then implement a 'where' in the mapping of the bag:
<bag name="Versions" cascade="all-delete-orphan" inverse="true" lazy="false" where="CurrentVersionNumber = Versions.VersionNumber" >
<key column="DataObjectId" />
<one-to-many class="DataObjectVersion" />
</bag>
note that in the 'where' you write proper SQL not HQL and as such the proper SQL i write above probably has to be changed to reflect your schema
Additionally if a single object is to be fetched setting up a bag and the according IList may be an overkill.
Applying a formula property and a DataObjectVersion object in the class may be more appropriate
in the class DataObject replace the IList with
public virtual DataObjectVersion Version { get; set; }
and in the mapping replace the 'bag' with something in the lines of
<property name="Version" type="DataObjectVersion" update="false" insert="false" formula="(select v.DataObjectVersionId, v.Comments, v.VersionNumber, v.DataObjectId from DataObjectVersion v where v.VersionNumber = CurrentVersionNumber)" />
again only proper SQL is allowed
i've used computed properties with native datatypes (datetime, string etc) and fetching an Entity may (or may not) need something more or different
Last but not least, you could apply a filter on the collection after you have fetched the primary object DataObject by creating a filter on the collection
IList<DataObjectVersion> fVersion =
NHibernateSession.CreateFilter(do.Versions, "where VersionNumber = :ver")
.SetParameter("ver", do.CurrentVersionNumber)
.List<DataObjectVersion>();
where the do.Versions collection is not initialized, only the results fetched in the separate fVersion collection and this is a second SELECT after already having made the round-trip to the db for the DataObject fetch.
Presumably your VersionNumber increments as the user changes the data and you're trying to get the latest one. If you consider the VersionNumber as an "Age" field instead (i.e. where 0 is the latest / youngest version, 1 is the next oldest and so on) then your problems becomes how to get all the entities with an Age of 0. This can be done using a filter: http://nhibernate.info/doc/nh/en/index.html#objectstate-filters

NHibernate bidirectional many-to-many association

I have a class with following description:
public class Customer {
public ISet<Client> Contacts { get; protected set;}
}
I want to map Contacts property onto following table:
CREATE TABLE user_contacts (
user1 uuid NOT NULL,
user2 uuid NOT NULL
)
I want it to map bidirectionally, i.e. when Customer1 added to Customer2's Contacts, Customer1's Contacts collection should contain Customer2 (maybe only after entity reload). How could I do that?
Update Sure I can map left-to-right and right-to-left sets and then combine then at runtime, but it'll... hmm... untasty... Is there other solution? Any way, thank you very match, FryHard!
Take a look at this link on what hibernate calls unidirectional many-to-many associations. In Castle ActiveRecord I make use of HasAndBelongsToMany links, but I am not sure how exactly it is mapped in nhibernate.
Though taking a look at your question a little deeper, it looks like you will be linking bidirectionally from customer to user_contacts, which could break the many-many link. I will play with an example and see what I can come up with.
An Export of the hbm files from ActiveRecord shows this
<?xml version="1.0" encoding="utf-16"?>
<hibernate-mapping auto-import="true" default-lazy="false" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:nhibernate-mapping-2.2">
<class name="NHibernateMapping.Customer, NHibernateMapping" table="Customer" schema="dbo">
<id name="Id" access="property" column="Id" type="Int32" unsaved-value="0">
<generator class="identity">
</generator>
</id>
<property name="LastName" access="property" type="String">
<column name="LastName" not-null="true"/>
</property>
<bag name="ChildContacts" access="property" table="user_contacts" lazy="false">
<key column="user1" />
<many-to-many class="NHibernateMapping.Customer, NHibernateMapping" column="user2"/>
</bag>
<bag name="ParentContacts" access="property" table="user_contacts" lazy="false" inverse="true">
<key column="user2" />
<many-to-many class="NHibernateMapping.Customer, NHibernateMapping" column="user1"/>
</bag>
</class>
</hibernate-mapping>
ActiveRecord example:
[ActiveRecord("Customer", Schema = "dbo")]
public class Customer
{
[PrimaryKey(PrimaryKeyType.Identity, "Id", ColumnType = "Int32")]
public virtual int Id { get; set; }
[Property("LastName", ColumnType = "String", NotNull = true)]
public virtual string LastName { get; set; }
[HasAndBelongsToMany(typeof(Customer), Table = "user_contacts", ColumnKey = "user1", ColumnRef = "user2")]
public IList<Customer> ChildContacts { get; set; }
[HasAndBelongsToMany(typeof(Customer), Table = "user_contacts", ColumnKey = "user2", ColumnRef = "user1", Inverse = true)]
public IList<Customer> ParentContacts { get; set; }
}
Hope it helps!