I have the following mapping:
<?xml version='1.0' encoding='utf-8'?>
<hibernate-mapping
assembly='Core'
namespace='Core.Models'
xmlns='urn:nhibernate-mapping-2.2'>
<class name='Basket'>
<id name='Id'
column='id'>
<generator class='native'/>
</id>
<property name="ExternalId" />
<map name="Items" table="BasketItems" cascade="save-update">
<key column="BasketId" />
<index-many-to-many class="Product" column="ProductId" />
<element column="Quantity" type="System.Int32" />
</map>
</class>
</hibernate-mapping>
This is what the Items collection looks like:
public virtual IDictionary<Product, int> Items { get; private set; }
And I have an Add method like so:
public virtual void Add(Product product, int quantity)
{
if (Items.ContainsKey(product))
Items[product] += quantity;
else
Items.Add(product, quantity);
}
Then the client code looks a bit like this:
var basket = new Basket();
basket.Add(session.Load<Product>(productId));
session.SaveOrUpdate(basket);
Now, the issue is that this client code does save the Basket to the basket table, but does not save any items to the BasketItems table (I'm using SQL Server 2005). However, this test against an in-memory db passes:
[Test]
public void Can_save_basket_with_products() // Passes!!!
{
var b = new Basket();
b.Add(_savedProduct);
_session.SaveOrUpdate(b);
_session.Flush();
_session.Evict(b);
var fromDb = _session.Load<Basket>(b.Id);
Assert.AreNotSame(b, fromDb);
Assert.IsTrue(fromDb.Items.ContainsKey(_savedProduct));
}
Any ideas on why it won't save when I'm against my actual DB? What am I missing?
Note: I translated my entities for this example, I hope it's still understandable even if I left something in portuguese ;-)
Well, found out that I was missing the Flush in my client code. I'm using Castle Monorail and the NHibernate Facility, so it looks as though NH Facility does not flush upon session closing. At least not in a web scenario. I'm assuming the session IS being closed after each request.
Related
Hi I have Request class which contains Document one-to-many mapping.
Request class
public class Request
{
virtual public int Id
{
get;
set;
}
...
virtual public Iesi.Collections.Generic.ISet<Document> Documents
{
get;
set;
}
}
Document class
public class Document
{
public virtual int Id
{
get;
set;
}
public virtual int ParentEntityId
{
get;
set;
}
}
XML Mapping looks like this:
REQUEST
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="xxxx"
namespace="xxxx.Domain">
<class name="Request" table="tbl_Req">
<id name="Id" column="req_id">
<generator class="native"></generator>
</id>
<set name="Documents" cascade="all-delete-orphan" inverse="false">
<key column="doc_parent_ent_id" not-null="true"/>
<one-to-many class="xxxx.Domain.Document"/>
</set>
</class>
</hibernate-mapping>
DOCUMENT looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="xxxx"
namespace="xxxx.Domain">
<class name="Document" table="tbl_doc">
<id name="Id" column="doc_id">
<generator class="native"></generator>
</id>
</class>
</hibernate-mapping>
Now at this configuration when I save document NHibernate creates
INSERT INTO tbl_doc (doc_digimage_code, doc_lnk_filename, doc_lnk_filepath, doc_timestamp, doc_author, doc_parent_ent_id) VALUES (#p0, #p1, #p2, #p3, #p4, #p5); select SCOPE_IDENTITY()',N'#p0 nvarchar(4000),#p1 nvarchar(4000),#p2 nvarchar(4000),#p3 datetime,#p4 int,#p5 int',#p0=N'1',#p1=NULL,#p2=NULL,#p3='2013-02-28 18:05:45',#p4=7353,#p5=174
and
UPDATE tbl_doc SET doc_parent_ent_id = #p0 WHERE doc_id = #p1',N'#p0 int,#p1 int',#p0=174,#p1=32
I dont unerstand why NHibernate generates INSERT and UPDATE when its updating field which already has correct value.
I also found this post NHibernate insert generates updates for collection items
which is suggesting using inverse, but when I add it to mapping then ParentEntityId is filled with 0.
Thanks
Change your Document class to refer Request class instead of ParentEntityId.
public class Document
{
public virtual int Id
{
get;
set;
}
public virtual Request ParentEntity
{
get;
set;
}
}
Set inverse = true on Documents which will not issue additional update statements when you try to insert new Requests.
<set name="Documents" cascade="all-delete-orphan" **inverse="true"**>
<key column="doc_parent_ent_id" not-null="true"/>
<one-to-many class="xxxx.Domain.Document"/>
</set>
But specifying inverse = true means that Document object should take care of the relationship by itself i.e., whenever a new document is added to the Request documents list be sure to set ParentEntity property.
Request objRequest = new Request();
objRequest.Documents = new Iesi.Collections.Generic.ISet<Document>()
{
new Document() { Id = 1, ParentEntity = objRequest }
};
By having inverse=false, which is by default, it is not required to set ParentEntity property, NHibernate will automatically detect the relationship. But it comes with additional update statement which we are trying to avoid here.
Finally, include many-to-one relationship in Document mapping.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="xxxx"
namespace="xxxx.Domain">
<class name="Document" table="tbl_doc">
<id name="Id" column="doc_id">
<generator class="native"></generator>
</id>
**<many-to-one cascade="none" class="xxx.Request" name="ParentEntity">
<column name="doc_parent_ent_id" not-null="true" />
</many-to-one>**
</class>
</hibernate-mapping>
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
Given the model Activity containing a bag with models of type Report (one to many). I would like to get a list of all activities, containing the number of reports of each activity. This two queries don't lead to any good, the counter is always 1 (which is wrong):
select act, (select count(r) from act.Reports r) from Activity act
Or:
select act, count( elements(act.Reports) ) from Activity act group by act.ActivityId, act.Title
Is it possible to write a proper query in HQL to solve this easy task?
Thx for any tipps!
sl3dg3
Edit:
Following the mappings. Activity:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class
name="Core.Models.Config.Activity, Core"
table="Activity"
>
<!-- primary key -->
<id name="ActivityId" type="Int32" unsaved-value="0" access="property">
<column name="ActivityId" not-null="true"/>
<generator class="identity" />
</id>
<!-- Properties -->
<many-to-one name="Title" fetch="join" cascade="all"/>
<!-- One-To-Many Reports -->
<bag name="Reports" inverse="true" fetch="join">
<key column="ReportId" />
<one-to-many class="Core.Models.Report"/>
</bag>
</class>
</hibernate-mapping>
Mapping Report:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class
name="Core.Models.Report, Core"
table="Report"
>
<!-- primary key -->
<id name="ReportId" type="Int32" unsaved-value="0" access="property">
<column name="ReportId" not-null="true"/>
<generator class="identity" />
</id>
<!-- Properties (shortened - there are more many-to-one and one bag -->
<property name="Created" />
<many-to-one name="Activity" column="ActivityId" />
</class>
Class Activity:
public class Activity
{
public Activity()
{
Title = new Translation();
}
public virtual int ActivityId { get; set; }
public virtual Translation Title { get; set; }
public virtual IList<Report> Reports { get; set; }
}
Class Report:
public class Report
{
/// <summary>
/// HNIBERNATE ONLY
/// </summary>
public Report()
{ }
/// <summary>
/// Init Report
/// </summary>
public Report(User author)
{
// ... Shortened
Activity = new Activity();
}
public virtual Activity Activity { get; set; }
}
What you want to achieve is something like this in SQL:
select
act.*,
(select count(*) from Reports rep where rep.id = act.reportId)
from Activity act
It would be easiest using size(), but unfortunately this is not working:
select act, size(act.Reports)
from Activity act
According to the docs, size is not available in the select clause. Interestingly, it actually works with .size, but not with size(), which should actually be equivalent:
select act, act.Reports.size
from Activity act
It may be worth a feature request to also make the function syntax (size()) working.
The officially working group by syntax is cumbersome, because you need to group by all mapped Activity properties:
select act, count(*)
from Activity act left join act.Reports rep
group by act.id, act.Name, act.Whatever
So I tried finally this variant and it seems to be exactly what you need:
select act, (select count(*) from act.Reports)
from Activity act
Your first HQL query has the wrong syntax. Try this instead:
select act, (select count(*) from act.Reports) from Activity act
Your second HQL query cannot work, because you would need all the columns in the GROUP BY clause. Try this instead:
select act.ActivityId, act.Title, count( elements(act.Reports) )
from Activity act
group by act.ActivityId, act.Title
Edit:
Ah, I think this might be the bug:
<bag name="Reports" inverse="true" fetch="join">
<key column="ActivityId" /> <-- instead of ReportId
<one-to-many class="Core.Models.Report"/>
</bag>
I'm new to nhibernate, and I'm sorry if this is answered elsewhere, but I've been looking for the last couple of hours, and can't find a solution that works.
A bit of background:
I'm trying to write an Admin area where there are users and sites, and a user can have access to multiple sites - but at various permission levels for each site.
Ideally I would like my classes look like this.
namespace MyApp.Users
{
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Password { get; set; }
public virtual IList<AdminUserSite> Sites { get; set; }
}
public class AdminUserSite
{
public virtual int UserTypeId { get; set; }
public virtual Site AdminSite { get; set; }
public virtual IList<Permission> Permissions { get; set; }
}
public class Permission
{
public virtual int Id { get; set; }
public virtual int AreaID { get; set; }
public virtual bool CanView { get; set }
public virtual bool CanEdit { get; set }
}
}
namespace MyApp.Sites
{
public class Site
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
}
}
And my database schema looks like this
f_user
{
f_user_id (int, PK)
name (nvarchar(50))
password (nvarchar(25))
}
f_user_site
{
f_user_id (int, PK)
f_site_id (int, PK)
d_user_type_id (int)
}
f_perm
{
f_perm_id (int, PK)
f_site_id (int)
f_user_id (int)
d_area_id (int)
can_read (bit)
can_write (bit)
}
f_site
{
f_site_id (int, PK)
title (nvarchar(50))
}
And the hibernate mapping files currently look like:
Users.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyApp"
namespace="MyApp.Users"
default-lazy="true">
<class name="User" table="f_user">
<id name="Id" column="f_user_id">
<generator class="identity" />
</id>
<property name="Name" column="name" />
<property name="Password" column="password" />
<bag name="Sites" table="f_user_site" inverse="true" cascade="all-delete-orphan">
<key column="f_user_id"/>
<one-to-many class="AdminUserSite"/>
</bag>
</class>
<class name="Permission" table="f_perm">
<id name="Id" column="f_perm_id">
<generator class="identity" />
</id>
<property name="AreaId" column="d_area_id" />
<property name="CanView" column="can_read" />
<property name="CanEdit" column="can_write" />
</class>
<class name="AdminUserSite" table="f_user_site">
<property name="UserTypeId" column="d_user_type_id" />
<many-to-one name="Site" class="MyApp.Sites.Site, MyApp.Sites" foreign-key="f_site_id"></many-to-one>
</class>
</hibernate-mapping>
and Sites.hbm.xml is
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyApp"
namespace="MyApp.Sites"
default-lazy="true">
<class name="Site" table="f_site">
<id name="Id" column="f_site_id">
<generator class="identity" />
</id>
<property name="Title" column="title" />
</class>
</hibernate-mapping>
Individually the User, Permission and Site classes all map fine - but i just can't figure out what AdminUserSite should be, and I haven't even attempted to put the permissions list in there yet.
Does anyone have any ideas?
Any help would be very appreciated.
Saan
I think you need to work on the database schema a bit. I think the AdminUserSite table is your problem.
Firstly, work out your entities (these should be simple nouns, which is why AdminUserSite seems out of place to me).
Entities: User, Permission, Site, SiteArea
Next, work out the relationships:
1 User has many Permissions
1 SiteArea has many Permissions
1 Site has many SiteAreas
(hope I got that right :) )
After that, your hbm and tables should flow more naturally.
Remember that normally you will have 1 table per entity, unless you have a many-many relationship (in which case you will need a joining table).
Generally a relation table mapping look like this :
<class name="AdminUserSite" table="f_user_site">
<composite-id >
<key-many-to-one name="Site" column="f_site_id" class="Site" />
<key-many-to-one name="User" column="f_user_id" class="User"/>
</composite-id>
</class>
In that way you can access either Site or User by its relation object or load relation object by criteria based on Site or User.
By the way, if your Permission object contains reference to user and site you may not need the AdminUserSite relation, the permission mapping already do it.
Edit about your comment Kind of replicating the same information in two spots.
Since NHibernate -like all ORM- has a first level cache you don't have to matter if your object can be accessed in two ways. You just have to ensure that your mappings are usefull to optimize and design your application well.
In this case, it's not a 'replication' but a reference. The object will be the same in the two spots if loaded by/retrieved from the same NHibernate session.
Accessing an object in two ways if they have a logic (ie. direct way and cross-relation way) is not an heresy.
This is most a data layer work to achieve to provide right methods to access the right objects in the right way :)
we have an old, big asp.net application with nhibernate, which we are extending and upgrading some parts of it. NHibernate that was used was pretty old ( 1.0.2.0), so we decided to upgrade to ( 2.1.2) for the new features. HBM files are generated through custom template with MyGeneration. Everything went quite smoothly, except for one thing.
Lets say we have to objects Blog and Post. Blog can have many posts, so Post will have many-to-one relationship. Due to the way that this application operates, relationship is done not through primary keys, but through Blog.Reference column.
Sample mapings and .cs files:
<?xml version="1.0" encoding="utf-8" ?>
<id name="Id" column="Id" type="Guid">
<generator class="assigned"/>
</id>
<property column="Reference" type="Int32" name="Reference" not-null="true" />
<property column="Name" type="String" name="Name" length="250" />
</class>
<?xml version="1.0" encoding="utf-8" ?>
<id name="Id" column="Id" type="Guid">
<generator class="assigned"/>
</id>
<property column="Reference" type="Int32" name="Reference" not-null="true" />
<property column="Name" type="String" name="Name" length="250" />
<many-to-one name="Blog" column="BlogId" class="SampleNamespace.BlogEntity,SampleNamespace" property-ref="Reference" />
</class>
And class files
class BlogEntity
{
public Guid Id { get; set; }
public int Reference { get; set; }
public string Name { get; set; }
}
class PostEntity
{
public Guid Id { get; set; }
public int Reference { get; set; }
public string Name { get; set; }
public BlogEntity Blog { get; set; }
}
Now lets say that i have a Blog with Id 1D270C7B-090D-47E2-8CC5-A3D145838D9C and with Reference 1
In old nhibernate such thing was possible:
//this Blog already exists in database
BlogEntity blog = new BlogEntity();
blog.Id = Guid.Empty;
blog.Reference = 1; //Reference is unique, so we can distinguish Blog by this field
blog.Name = "My blog";
//this is new Post, that we are trying to insert
PostEntity post = new PostEntity();
post.Id = Guid.NewGuid();
post.Name = "New post";
post.Reference = 1234;
post.Blog = blog;
session.Save(post);
However, in new version, i get an exception that cannot insert NULL into Post.BlogId. As i understand, in old version, for nhibernate it was enough to have Blog.Reference field, and it could retrieve entity by that field, and attach it to PostEntity, and when saving PostEntity, everything would work correctly. And as i understand, new NHibernate tries only to retrieve by Blog.Id.
How to solve this? I cannot change DB design, nor can i assign an Id to BlogEntity, as objects are out of my control (they come prefilled as generic "ojbects" like this from external source)
It seems very strange to me that the code worked in NH 1. But, since it is not working at the moment anyway, I think you have to look for the blog entity in a query first:
var criteria = DetachedCriteria.For<Blog>();
criteria.Add(Expression.Eq("Reference", 1));
var blog = criteria.GetExecutableCriteria(session).List<Blog>().FirstOrDefault();
post.Blog = blog;
session.Save(post);
this
blog.Id = Guid.Empty
is translated as a null in the DB. So when you change it (as the sample code implies) you are explicitly setting a null value on the BlogEntity Id.
This is the error you are receiving and is irrelevant of the "Reference" column/property.
As for the question of what you can do... well you don't have to make the ORM joins on the Guids! You can make the joins on the Reference column...
Answering my own question.
The problem was that nhibernate was hiting DB to retrieve BlogEntity with id 00000000-0000-0000-0000-000000000000. Of course in DB it got nothing, so it tried to insert null
And it was clearly visible in logs why it was happening
Unable to determine if BlogEntity with
assigned identifier
00000000-0000-0000-0000-000000000000
is transient or detached; querying
the database. Use explicit Save() or
Update() in session to prevent this.
Solved it my implementing IInterceptor, passing it to Session and especially its method bool? IsTransient(object entity)
And problem solved.