I have an IDictionary on an object which I'm loading with the following mapping:
public class InternalFund : IInternalFund
{
public virtual IDictionary<DateTime, IValuation> Valuations { get; set; }
}
<class name="InternalFund">
<map name="Valuations">
<key>
<column name="FundID" />
</key>
<index type="DateTime" column="ValuationDate" />
<one-to-many class="Echo.EchoDomain.Portfolio.Valuation" />
</map>
</class>
This works fine, the Valuation object doesn't have a ValuationDate on it but Nhibernate is loading the ValuationDate into the key of the dictionary as desired. I want to query the InternalFund retrieving just one Valuation specifying the ValuationDate. I've managed to do this using the index() function in HQL:
"from InternalFund i left join fetch i.Valuations v where index(v)='2009-09-30'"
Again this is fantastic and exactly what I want producing the following where clause:
((valuations1_.ValuationDate='2009-09-30' ))
But I'd really like to do this in a DetachedCriteria to preserve the sanity of my project. When I try
.Add(Restrictions.Eq("index(Valuations)", valuationDate));
Or
.CreateAlias("Valuations", "v", JoinType.LeftOuterJoin)
.Add(Restrictions.Eq("index(v)", valuationDate));
It says:
QueryException: could not resolve property: index(v) of: Echo.EchoDomain.Fund.InternalFund
Is there a way to run index() with a DetachedCriteria?
Thanks
Stu
I believe that it is not possible (yet?)
See this feature-request / improvement request on NHibernate JIRA.
Related
i have old legacy DB which has dead links in their tables. I have class mapped in nhibernate like this:
<class name="Visible" table="table_visible">
<composite-id>
<key-many-to-one column="object_id" name="ObjectA" />
<key-many-to-one column="sub_object_id" name="SubObject" />
</composite-id>
<property column="visible" name="VisibleRow" />
</class>
and:
public class Visible
{
public virtual ObjectAClass ObjectA { get; set; }
public virtual SubObjectClass SubObject { get; set; }
public virtual bool VisibleRow { get; set; }
public override bool Equals(object obj)
{
var other = ((Visible)obj);
return this.ObjectA.Equals(other.ObjectA) && this.SubObject.Equals(other.SubObject);
}
public override int GetHashCode()
{
return this.ObjectA.GetHashCode() + (this.SubObject != null? this.SubObject.GetHashCode(): 0);
}
}
Now all works fine when all joins in database are correct, but when i find such sub_object_id which doesnt have entity, nhibernate throws me error
No row with the given identifier exists:[SubObject#123]
Is there a way to map composite key so that when its subentity is not found, the whole entity wouldnt be loaded (like with inner join)?
NHibernate v2.0.50727
Following Daniel Schilling idea of fetch Visible entities with a where exists sub-query, found that there is loader element available in mappings.
<class name="ObjectA" table="table_object">
.........
<set name="VisibleList" cascade="all" lazy="false" inverse="true">
<key column="object_id" />
<one-to-many class="Visible" />
<loader query-ref="valid_entities"/>
</set>
</class>
<sql-query name="valid_entities">
<load-collection alias="v" role="ObjectA.VisibleList"/>
SELECT {v.*}
FROM table_visible v
INNER JOIN table_sub_entities e ON e.sub_entity_id=v.sub_entity_id
WHERE v.object_id=?
</sql-query>
And nothing else needed to be changed.
<key-many-to-one column="sub_object_id" name="SubObject" not-found="ignore" />
... may be helpful. From the NHibernate Documentation...
ignore will treat a missing row as a null association
Please be aware of the performance penalty associated with using this option. Whenever NHibernate fetches a Visible entity, it will also have to fetch SubObject. If you don't go ahead and fetch it in your query, this means that NHibernate will be issuing lots of lazy loads.
This doesn't meet your "when its sub-entity is not found, the whole entity wouldn't be loaded" goal. Instead NHibernate would give you an entity with a null sub-entity. If you want that inner-join-like behavior, then I think you would need to fetch your Visible entities with a where exists sub-query to make sure the SubObject actually exists.
The best option would be to fix the data in the database and add a foreign key constraint.
I just ran across this: Relations with not-found="ignore". I promise I'm not copying Ricci's content - I'm writing this from my own experience.
Consider this class that represents a node in a hierarchical structure:
public class Node
{
public Node()
{
Children = new List<Node>();
}
public virtual int Id { get; set; }
public virtual IList<Node> Children { get; set; }
public virtual Node Parent { get; set; }
public virtual int Position
{
get { return Parent == null ? -1 : Parent.Children.IndexOf(this); }
set { }
}
}
The mapping looks like this (as NHibernate does not support lists in bidirectional associations, I use a bag here and have the children determine their position automatically):
<?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="AmbiguousColumn" assembly="AmbiguousColumn" xmlns="urn:nhibernate-mapping-2.2">
<class name="Node">
<id name="Id" type="Int32">
<generator class="identity" />
</id>
<bag name="Children" inverse="true" cascade="all" order-by="Position">
<key column="Parent" />
<one-to-many class="Node" />
</bag>
<many-to-one name="Parent" />
<property name="Position" />
</class>
</hibernate-mapping>
To get all nodes with their children loaded I'd use a query like this:
var nodes = session.QueryOver<Node>()
.Fetch(x => x.Children).Eager
.List();
However, executing this results in an exception:
NHibernate.Exceptions.GenericADOException: could not execute query
[...(sql)...] ---> System.Data.SqlClient.SqlException: Ambiguous column name 'Position'.
The SQL:
SELECT
this_.Id as Id0_1_,
this_.Parent as Parent0_1_,
this_.Position as Position0_1_,
children2_.Parent as Parent3_,
children2_.Id as Id3_,
children2_.Id as Id0_0_,
children2_.Parent as Parent0_0_,
children2_.Position as Position0_0_
FROM
Node this_
left outer join
Node children2_
on this_.Id=children2_.Parent
ORDER BY
Position
I understand why this happens: NH joins the same table twice, but uses the order clause without qualifying the column name.
The question is: how can I make this scenario work? Resorting to instead of is probably difficult as I'd like to have a bidirectional relation.
There are a couple of similar question on SO, but nowhere did I find an actual solution.
Update: the error is database/driver specific. Using the Sql Server CE (e.g. SqlServerCeDriver and MsSqlCe40Dialect) I get the proper query. Using Sql Server (e.g. Sql2008ClientDriver and MsSql2012Dialect) produces the unqualified queries.
According to my own tests, this behavior still exists in the master branch on github.
A gist with a test case: https://gist.github.com/anonymous/5377535
I think I found the cause of the problem and viable workarounds:
The cause of the issue is the fact that the column is called "Position", which is a reserved word in ODBC according to http://msdn.microsoft.com/en-us/library/ms189822.aspx
This combined with the fact that the default value for NH's hbm2ddl.keywords property is set to "keywords" somehow caused NH not to qualify the order-by clause, probably because it though "Position" was a keyword, not a column.
http://nhforge.org/blogs/nhibernate/archive/2009/06/24/auto-quote-table-column-names.aspx
Ways to fix it:
1) Use a different name for the property - one that isn't a keyword. In this case, PositionInParent would have worked without any issues.
2) Quote the order by clause properly using back-ticks.
<bag name="Children" inverse="true" cascade="all" order-by="`Position`">
Or whatever it takes in your mapping API of choice, e.g. in mapping by code:
cls.Bag(x => x.Children,
map =>
{
map.Inverse(true);
map.Cascade(Cascade.All);
map.Key(key => key.Column("Parent"));
map.OrderBy("`Position`"); // note that you must not use a lambda expression in this case
},
map => map.OneToMany());
3) Disable keyword auto import, ie. set hbm2ddl.keywords to none (neither keywords nor auto-quote will work):
<property name="hbm2ddl.keywords">none</property>
Or programmatically:
config.DataBaseIntegration(db => db.KeywordsAutoImport = Hbm2DDLKeyWords.None);
You can still auto-quote reserved words by calling SchemaMetadataUpdater.QuoteTableAndColumns just before building the session factory.
SchemaMetadataUpdater.QuoteTableAndColumns(config);
I'll stick with approach 3 for now as it is the most painless so far.
I ran into a strange NHibernate and Automapper problem. I am not sure which one is to blame but I am struggling for a whole day now and I can't seem to find out why.
Here is my Nhibernate mapping files:
Navigation.hbm.xml
<id name="ID" column="NavigationID">
<generator class="identity"></generator>
</id>
<property name="IsDefault"/>
<property name="RoleType" column="RoleTypeID" />
<bag name="Items" cascade="save-update" inverse="true" lazy="false" fetch="join">
<key column="NavigationID"/>
<one-to-many class="NavigationItem"/>
</bag>
NavigationItem.hbm.xml
<class name="NavigationItem" table="NavigationItem">
<id name="ID" column="NavigationItemID">
<generator class="identity"></generator>
</id>
<property name="ShowInMenu"/>
<property name="Order" column="[Order]" />
<many-to-one name="Page" column="PageID" lazy="false" fetch="join" />
<many-to-one name="Navigation" column="NavigationID" />
<many-to-one name="Parent" column="ParentNavigationItemID" />
<bag name="Items" cascade="save-update" inverse="true">
<key column="ParentNavigationItemID"/>
<one-to-many class="NavigationItem"/>
</bag>
This is how I fill up a Navigation object:
ISession session = SessionProvider.Instance.CurrentSession;
using (transaction = session.BeginTransaction())
{
var navigation = session.QueryOver<Navigation>()
.Where(x => x.IsDefault && x.RoleType == null)
.TransformUsing(new NHibernate.Transform.RootEntityResultTransformer())
.SingleOrDefault();
transaction.Commit();
return navigation;
}
Since the Items bag on the Navigation object is set to lazy="false", I get only one query to the database to get the Navigation object and a left join to get all the Navigation items as well.
All is perfect until now.
I did a test to iterate through all the items and the sub-items recursive and no more hits to the database.
Then, I have an UI model that I map with Automapper.
Here are the UI models:
public class NavigationModel
{
public List<NavigationItemModel> Items { get; set; }
public NavigationModel()
{
Items = new List<NavigationItemModel>();
}
}
public class NavigationItemModel
{
public string PageName { get; set; }
public string Url { get; set; }
public bool Selected { get; set; }
public NavigationItemModel Parent { get; set; }
public List<NavigationItemModel> Items { get; set; }
}
And the automapper mappings:
AutoMapper.Mapper
.CreateMap<NavigationItem, NavigationItemModel>()
// IF I REMOVE THE NEXT LINE, IT HITS THE DATABASE FOR EACH SUB-ITEM of the NavigationItem.Items
.ForMember(m => m.Items, o => o.Ignore());
AutoMapper.Mapper
.CreateMap<Navigation, NavigationModel>();
Ok, now the behavior is like this:
If I ignore the NavigationItem.Items member in the mapping, all goes well, but only the Navigation and it's items are mapped. No sub-items collection of the navigation's Items are mapped. BUT the database is not hit anymore. But I want the other items mapped as well...
If I remove the line under the comment, the database is hit for each of the Navigation.Items, querying for it's sub-items (where ParentID = Item.ID).
Any idea what am I doing wrong?
Sorry for the wall of text, but I thought better to describe it in more detail, I spent the whole day on this one and I tried all kind of queries with Future and JoinQueryOver, etc. The problem does not seem to be with NHibernate since that loads fine and I can iterate without any more calls to the database.
I forgot to include the SQL that is being generated:
First there is this query:
SELECT this_.NavigationID as Navigati1_7_2_,
this_.IsDefault as IsDefault7_2_,
this_.RoleTypeID as RoleTypeID7_2_,
items2_.NavigationID as Navigati5_4_,
items2_.NavigationItemID as Navigati1_4_,
items2_.NavigationItemID as Navigati1_4_0_,
items2_.ShowInMenu as ShowInMenu4_0_,
items2_.[Order] as column3_4_0_,
items2_.PageID as PageID4_0_,
items2_.NavigationID as Navigati5_4_0_,
items2_.ParentNavigationItemID as ParentNa6_4_0_,
page3_.PageID as PageID8_1_,
page3_.Name as Name8_1_,
page3_.Title as Title8_1_,
page3_.Description as Descript4_8_1_,
page3_.URL as URL8_1_
FROM Navigation this_
left outer join NavigationItem items2_
on this_.NavigationID = items2_.NavigationID
left outer join Page page3_
on items2_.PageID = page3_.PageID
WHERE (this_.IsDefault = 1 /* #p0 */
and this_.RoleTypeID is null)
Then, when Automapper comes into play, a list of these queries are being generated, only the p0 parameter differs (from 1 to 12 ... the number of items without parents )
SELECT items0_.ParentNavigationItemID as ParentNa6_2_,
items0_.NavigationItemID as Navigati1_2_,
items0_.NavigationItemID as Navigati1_4_1_,
items0_.ShowInMenu as ShowInMenu4_1_,
items0_.[Order] as column3_4_1_,
items0_.PageID as PageID4_1_,
items0_.NavigationID as Navigati5_4_1_,
items0_.ParentNavigationItemID as ParentNa6_4_1_,
page1_.PageID as PageID8_0_,
page1_.Name as Name8_0_,
page1_.Title as Title8_0_,
page1_.Description as Descript4_8_0_,
page1_.URL as URL8_0_
FROM NavigationItem items0_
left outer join Page page1_
on items0_.PageID = page1_.PageID
WHERE items0_.ParentNavigationItemID = 1 /* #p0 */
This is taken from the NHProf application, hope it helps.
Thank you,
Cosmin
I think AutoMapper is mapping your classes recursively. If this is the case, than you can specifiy the max depth for your mappings using
Mapper.CreateMap<TSource, TDestination>().MaxDepth(2); // or 1, or 3, or whatever
I am creating a survey application where I have a survey which has a collection of pages. Each page will have a collection of questions and each question will have a collection of answer options. My class structure looks like:
public class Survey : Entity {
public IList<Page> Pages { get; set; }
}
public class Page : Entity {
public IList<Question> Questions { get;set; }
}
public class Question : Entity {
public IList<Option> Options { get; set; }
}
public class Option : Entity {}
The mapping for each class is:
<!-- mapping for ID and other properties excluded -->
<class name="Survey">
<bag name="Pages" generic="true" inverse="true">
<key column="SurveyId" />
<one-to-many class="Page" />
</bag>
<bag name="Questions" access="none">
<key column="SurveyId" />
<one-to-many class="Question" />
</bag>
</class>
<class name="Page">
<many-to-one name="Survey" column="SurveyId" />
<bag name="Questions" generic="true" inverse="true">
<key column="PageId" />
<one-to-many class="Question" />
</bag>
</class>
<class name="Question">
<many-to-one name="Page" column="PageId" />
<many-to-one name="Survey" column="SurveyId" />
<bag name="Options" generic="true" inverse="true">
<key column="QuestionId" />
<one-to-many class="Option" />
</bag>
</class>
<class name="AnswerOption">
<many-to-one name="Question" column="QuestionId" />
</class>
I need to display all the questions on a page so I start with the survey object and loop through the pages, items and options. This causes NHibernate to execute many queries and I would like to optimize this. How can I get the survey object with the nested collections in the best possible way without executing too many queries?
This is the code I have at the moment but it still executes many queries:
var result = Session.CreateMultiQuery()
.Add(Session.CreateQuery("from Survey s inner join fetch s.Pages where s.Id = :id"))
.Add(Session.CreateQuery("from Survey s inner join fetch s.Question where s.Id = :id"))
.SetInt32("id", id)
.List();
IList list = (IList)result[0];
return list[0] as Survey;
I have also tried Future queries but they don't help to reduce the number of queries.
Any ideas?
If I understood correctly, you can achieve this with the following HQL (of course the same idea can be used with ICriteria)
from Survey s
inner join fetch s.Pages p
inner join fetch p.Questions q
inner join fetch q.Options
where s.Id = :id
If you also want to fetch survey.Questions then the best option is to fetch those in separate qyery and used Futures to avoid cartesian product.
Also, if I remember correctly HQL queries ignores fetch's defined in mappings. If the collection is mapped with lazy="false" fetch="join" then it is fetch when used ICriteria but not when using HQL.
You could try adding
lazy="false" fetch="join"
to your bag declarations. That way you can be sure the bag will be fetched using one query.
A solution I'm using is reversing relation between entities. With this you can be sure when a record of Survey is loaded, no record of Page is loaded unless you call LoadAll method. LoadAll is a method in each class that search all related records. Consider following code:
public class Survey : Entity {
Survey[] LoadAll {
string hql = "from Page page where page.SurveyID =" + this.ID;
//....
}
}
public class Page : Entity {
public Survey { get;set; }
}
public class Question : Entity {
public Page Page { get; set; }
}
public class Option : Entity {
public Question Question {set; get;}
}
I have a tree where every node is a Resource class:
public abstract class Resource
{
public virtual Guid Id { get; set; }
public virtual Resource Parent { get; set; }
public virtual IList<Resource> ChildResources { get; set; }
}
as you can see this class is abstract and there are many different derived classes from Resource (3 at the moment, more to come).
In my database i have a table for Resource, and a table for each class which
derives from Resource. These are mapped together with <joined-subclass>.
I've read this:
http://ayende.com/Blog/archive/2009/08/28/nhibernate-tips-amp-tricks-efficiently-selecting-a-tree.aspx
and i have the same code as Ayende to load my tree:
var resource = UnitOfWork.Current.Session
.CreateQuery("from Resource r join fetch r.ChildResources")
.SetResultTransformer(new DistinctRootEntityResultTransformer())
.SetReadOnly(true)
.List<Resource>();
which is all working fine (all Resources are returned with a single select) However, I'm seeing extra selects occurring as I enumerate a Resource's ChildResources list.
Is that because of this?:
http://ayende.com/Blog/archive/2009/09/03/answer-the-lazy-loaded-inheritance-many-to-one-association-orm.aspx
Either way, how do I prevent this from happening?
Here's the part of the mappings for the relationships (class names
trimmed for clarity):
<bag cascade="save-update" fetch="join" lazy="false" inverse="true" name="ChildResources">
<key>
<column name="Parent_Id" />
</key>
<one-to-many class="Resource" />
</bag>
<many-to-one class="Resource" name="Parent">
<column name="Parent_Id" />
</many-to-one>
Thanks
UPDATE
Slight oversight, its only issuing extra selects when enumerating the child collections of the leaf nodes in the tree...
Either do this:
<bag ... lazy="false">
to eager fetch the items always, or do this (in HQL):
var resources = session.CreateQuery("from Resource r join fetch r.ChildResources");