I have one-to-one relationship between two tables, and I want to be able to create a LINQ query that will return the "parent" tables where there is something in the child table. The problem is that the query NH is generating is checking to see if the parent table's ID is not null (and it never is) rather than joining to the child table. This is regardless of whether I use lazy or non-lazy loading. I'm using custom automap conventions with an override, but here's the HBM XML that gets generated:
Mapping for Abstract Class, Concrete Class that is Parent
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class xmlns="urn:nhibernate-mapping-2.2" name="AbstractClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" table="ABSTRACT_CLASS">
<id name="AbstractClassId" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="ABSTRACT_CLASS_ID" />
<generator class="identity" />
</id>
<joined-subclass name="ConcreteClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" table="CONCRETE_CLASS">
<key>
<column name="ABSTRACT_CLASS_ID" />
</key>
<one-to-one cascade="none" class="AuxiliaryClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" name="AuxiliaryClass" property-ref="Foo" />
</joined-subclass>
</class>
</hibernate-mapping>
Mapping for Child Table
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class xmlns="urn:nhibernate-mapping-2.2" name="AuxiliaryClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" table="AUXILIARY_CLASS">
<id name="AuxiliaryClassiD" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="AUXILIARY_CLASS_ID" />
<generator class="identity" />
</id>
<many-to-one class="ConcreteClass, DomainObjects, Version=2.2.1.0, Culture=neutral, PublicKeyToken=aaaaaaaaaaaaaaaa" name="Foo">
<column name="FOO_ABSTRACT_CLASS_ID" />
</many-to-one>
</class>
</hibernate-mapping>
Class Definitions
public abstract class AbstractClass
{
public virtual Int32? AbstractClassId { get; set; }
}
public class ConcreteClass : AbstractClass
{
public virtual AuxiliaryClass AuxiliaryClass { get; set; }
}
public class AuxiliaryClass
{
public virtual Int32? AuxiliaryClassId { get; set; }
public virtual ConcreteClass Foo { get; set; }
}
The LINQ query that isn't work is:
nh.Query<ConcreteClass>().Where(cc => cc.AuxiliaryClass != null);
The query that's being generated is:
select
concretecl0_.CONCRETE_CLASS_ID as CONCRETE1_0_
from
CONCRETE_CLASS concretecl0_
inner join
ABSTRACT_CLASS concretecl0_1_
on
concretecl0_.ABSTRACT_CLASS_ID=concretecl0_1_.ABSTRACT_CLASS_ID
where
concretecl0_.ABSTRACT_CLASS_ID is not null
If I turn off lazy loading, the joins out to the auxiliary table but still compares the concrete class's table's ID to null.
edit
Per #Suhas's suggestion:
Try changing your Linq query to nh.Query(cc => cc.AuxiliaryClass.AuxiliaryClassId > 0); assuming AuxiliaryClassId is of type int
I actually did cc => cc.AuxiliaryClass.AuxiliaryClassId != null, which worked, getting me this query:
select
concretecl0_.ABSTRACT_CLASS_ID as concretecl0_1_0_
from
CONCRETE_CLASS concretecl0_
inner join
ABSTRACT_CLASS concretecl0_1_
on
concretecl0_.concretecl0_=concretecl0_1_.concretecl0_
, AUXILIARY_CLASS auxiliaryc1_
where
concretecl0_.ABSTRACT_CLASS_ID=auxiliaryc1_.FOO_ABSTRACT_CLASS_ID
and (auxiliaryc1_.AUXILIARY_CLASS_ID is not null)
However, when I tried the inverse case, cc => cc.AuxiliaryClass.AuxiliaryClassId == null, I got a non-working query:
select
concretecl0_.ABSTRACT_CLASS_ID as concretecl0_1_0_
from
CONCRETE_CLASS concretecl0_
inner join
ABSTRACT_CLASS concretecl0_1_
on
concretecl0_.concretecl0_=concretecl0_1_.concretecl0_
, AUXILIARY_CLASS auxiliaryc1_
where
concretecl0_.ABSTRACT_CLASS_ID=auxiliaryc1_.FOO_ABSTRACT_CLASS_ID
and (auxiliaryc1_.AUXILIARY_CLASS_ID is null)
Just listing down my comments (slightly tailored to an answer) from the original questions as those seems to have helped the author of the question
Try changing your Linq query to nh.Query<ConcreteClass>(cc => cc.AuxiliaryClass.AuxiliaryClassId > 0); assuming AuxiliaryClassId is of type int
The above would not work when you want to fetch records present in ConcreteClass but not present in AuxiliaryClass. For that you might want to use left outer join. If you do left outer join, then you can do it via QueryOver (or Linq in a not so direct way) and then do the check for nullity in your code. Depending on the size of the dataset you load, this may not be a good thing to do in production. I am not sure how would do subquery to achieve what you are trying to achieve
if LINQ is the only option then you can use DefaultIfEmpty method to get left outer joins in LINQ. This SO question should be a good starting point.
Lastly a plain SQL could be most effective in this situation. If you want to go down that route then you can use ISession.CreateSQLQuery and use your SQL query as is
Related
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 trying to figure what's the correct way to map the following parent child relationship. I have a parent class which contains child objects. However, the parent also has a pointer to an instance of one of the children (the PrimaryChild)
Class Parent
Public Property Id As Integer?
Public Property PrimaryChild As Child
Public Property Children As IList(Of Child)
End Class
Public Class Child
Public Property Id As Integer?
Public MyParent As Parent
End Class
Usage is something like
Dim ch As New Child
Dim par as New Parent
ch.MyParent = par
par.Children.Add(ch)
par.PrimaryChild = ch
Session.SaveOrUpdate(par)
However, when I do this, the PrimaryChild is shown as being a null or transient value. I have set cascade="all" on the Children collection.
Any ideas what I'm doing wrong?
Update 1
Added Mappings
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Parent" table="Parents">
<id name="Id" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="ID" />
</id>
<set access="nosetter.camelcase-underscore" cascade="all-delete-orphan" inverse="true" name="Children" mutable="true">
<key>
<column name="ParentID" />
</key>
<one-to-many class="Child" />
</set>
<many-to-one cascade="save-update" class="Child" name="PrimaryChild">
<column name="PrimaryChildID" not-null="true" />
</many-to-one>
<many-to-one cascade="save-update" class="Child" name="SecondaryChild">
<column name="SecondaryChildID" not-null="true" />
</many-to-one>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Child" table="Child">
<id name="Id" type="System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="ID" />
</id>
<many-to-one class="Parent" name="Parent">
<column name="ParentID" not-null="true" />
</many-to-one>
</class>
</hibernate-mapping>
Your tables are looking like this:
Table Parent
(
...
PrimaryChild_FK NOT NULL
)
Table Child
(
...
Paren_FK NOT NULL
)
Can you tell me in which order the data should be inserted? You can neither insert Parent nor Child, since both need the other to set the foreign key. (NHibernate inserts one of them and set the FK to null, to update it later. But the database complains.)
Remove the not null constraint from the set. NHibernate is not smart enough to find a working insert order if you just remove one of them. (AFAIK, not null constraints in the mapping files are actually only used to create the database schema from).
And as already mentioned by mathieu, make the set inverse and use the same foreign key for the child-parent and the parent-children relations.
I've performed this task with tools like the NHibernate LINQ library.
public class Parent {
public Parent() {
Children = new List<Child>();
}
public virtual IList<Child> Children { get; set; }
public virtual Child PrimaryChild {
get {
return Children.FirstOrDefault(x => x.IsPrimary);
}
}
}
If you've already loaded the children, then PrimaryChild is an in-memory operation. If you request PrimaryChild first, then this will be it own database fetch operation. Works nicely, IMO.
And you should look at Andrew Bullock's response. Exposing an IList opens the door to bad domain design. You should only expose the enumeration.
What mappings do you have so far?
PrimaryChild is a one-to-one, Children is a one-to-many but you could manage the relationship in many different ways. Where are your Foreign Keys?
if you put the FK on the Child, then you want both one-to-one and one-to-many mappings with inverse=true set on both.
As an aside, this:
ch.MyParent = par
par.Children.Add(ch)
is a massive OO encapsulation fail. Parent shouldn't expose an IList, as other objects can manipulate it. The parent class should control all manipulations of itself. Make it an IEnumerable and use an AddChild method that does the above two lines.
If your relation is bidirectional (IE parent references children, and child references parent), and if the foreign key is on Child, you need to set the attribute
inverse="true"
in the declaration of the collection. Otherwise cascade won't work nicely :
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Parent">
<id name="Id" />
<bag name="Children" cascade="all" inverse="true">
<key column="ID_PARENT" />
<one-to-many class="Child"/>
</bag>
</class>
<class name="Children">
<id name="Id" />
<many-to-one name="Parent" column="ID_PARENT" class="Parent" not-null="true" />
</class>
</hibernate-mapping>
ok, I have difficult time with automapping collections using fluent nhibernate. This time I tried to apply a collection convention which simply says to use camelCaseField with underscore. Well I got the convention loaded and I hit the breakpoint in the method below FNH still produces strange mapping. What I am doing wrong?
public class Parent
{
public virtual int Id { get; set; }
private IList<Child> _testCollection;
public virtual IList<Child> TestCollection
{
get
{
return _testCollection;
}
}
}
public class Child
{
public virtual int Id { get; set; }
}
public class CollectionAccessConvention : ICollectionConvention
{
public void Apply( ICollectionInstance instance )
{
instance.Access.CamelCaseField( CamelCasePrefix.Underscore );
}
}
<class xmlns="urn:nhibernate-mapping-2.2" mutable="true" name="Test.Parent, Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Parent`">
<id name="Id" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<column name="Id" />
<generator class="identity" />
</id>
<bag access="nosetter.camelcase" name="TestCollection" mutable="true">
<key>
<column name="Parent_id" />
</key>
<one-to-many class="Test.Child, Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
EDIT: #Bary: the strange thing is access="nosetter.camelcase". I think it should be access="field.camelcase-underscore". Any suggestions?
I figure it out.
Conventions work little bit strange but over time I think I will fully realize it. When you want to apply a convention about certain property or anything it will be applied only if it is not configured/set/defined already. So, when fluent compiles the mappings it automatically sets the readonly properties to access="nosetter.camelcase". Fortunately there is a way to fix this.
The solution:
You have to define your own automapping configuration by extend DefaultAutomappingConfiguration class and then override the method public virtual Access GetAccessStrategyForReadOnlyProperty(Member member) OR just implement the IAutomappingConfiguration interface. After you are done you can add this configuration when you initialize the fluent configuration.
Fluently.Configure( Configuration )
.Mappings( cfg =>
{
cfg.AutoMappings.Add( *yourIAutomappingConfiguration* )
}
Suppose I have a database like this:
This is set up to give role-wise menu permissions.
Please note that, User-table has no direct relationship with Permission-table.
Then how should I map this class against the database-tables?
class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool? IsActive { get; set; }
public IList<Role> RoleItems { get; set; }
public IList<Permission> PermissionItems { get; set; }
public IList<string> MenuItemKeys { get; set; }
}
This means,
(1) Every user has some Roles.
(2) Every user has some Permissions (depending on to Roles).
(3) Every user has some permitted MenuItemKeys (according to Permissions).
How should my User.hbm.xml look like?
Roles and Permissions are likely to be accessed a lot in the application. They are very likely to be in the second level cache, which means we can expect to efficiently iterate the User.RoleItems and Role.Permissions.
This has the advantage that we can generally expect to perform no queries when iterating those collections.
You could map the classes as follows.
The properties User.PermissionItems and User.MenuItemKeys are derived from the persistent entities, and thus do not appear in the mappings.
<class name="User" table="user">
<id name="ID">
<generator class="native"/>
</id>
<property name="Name"/>
<property name="Username"/>
<property name="Password"/>
<property name="IsActive"/>
<bag name="RoleItems" table="userrole" lazy="true">
<key column="userid" />
<many-to-many class="Role" column="roleid"/>
</bag>
</class>
<class name="Role" table="role">
<id name="ID">
<generator class="native"/>
</id>
<property name="RoleName"/>
<property name="IsActive"/>
<bag name="Permissions" table="permission">
<key column="roleid" />
<one-to-many class="Permission"/>
</bag>
</class>
<class name="Permission" table="permission">
<id name="ID">
<generator class="native"/>
</id>
<property name="MenuItemKey"/>
</class>
I would make the 2 additional lists you had on User into derived enumerations. If they were lists, there is no unambiguous way to insert into them since you cannot know to which role the value applies. Also, a Role is not owned by a User.
Update: now using Diego's improved version of these properties.
class User
{
public virtual IEnumerable<Permission> PermissionItems
{
get {
return RoleItems.SelectMany(role => role.PermissionItems);
}
}
public virtual IEnumerable<string> MenuItemKeys
{
get {
return RoleItems.SelectMany(role => role.PermissionItems,
(role, permission) => permission.MenuItemKey);
}
}
}
The mapping posted by Lachlan is the best alternative. You could use queries that perform all the joins for each collection, but that'd make them read only for practical purposes.
There is a much easier way to implement the property code, however, that might help you decide:
public IEnumerable<Permission> PermissionItems
{
get
{
return RoleItems.SelectMany(role => role.PermissionItems);
}
}
public IEnumerable<string> MenuItemKeys
{
get
{
return RoleItems.SelectMany(role => role.PermissionItems,
(role, permission) => permission.MenuItemKey);
}
}
Here's a link: Chapter 6. Collection mapping
Here's another useful link: Chapter 7. Association Mappings
EDIT
After having reasearched for an entire evening, I came to the following conclusion:
Considering NHibernate Best Practices, what you wish to do is no good;
Don't use exotic association mappings.
Good usecases for a real many-to-many associations are rare. Most of the time you need additional information stored in the "link table". In this case, it is much better to use two one-to-many associations to an intermediate link class. In fact, we think that most associations are one-to-many and many-to-one, you should be careful when using any other association style and ask yourself if it is really neccessary.
As a programming philosophy, I prefer to keep it simple than having to write clever code where even me would no more understand what I wrote after a certain time;
Plus, I even considered using the subquery element of association mapping which would have worked if I would have found a way to parameterize it, if it is doable, but it seems it won't let me parameterize the query with the User instance's Id property value;
In the optic of a well designed OO model, a child being aware of his parent's properties is fine, but a parent accessing a child's property makes no sens - design smell;
As I may understand considering the context exposed the benefits of having permissions or MenuItemKey values accessible from the User directly, I suggest the following solution:
Create yourself a user defined dataview which will hold the values related to the MenuItemKey Permission attribute gotten through the Roles the User is a member like so:
CREATE VIEW udvUsersPermissions AS
SELECT UR.UserID, P.ID as N'ID', P.MenuItemKey
FROM Users U
INNER JOIN UsersRoles UR ON UR.UserID = U.ID
INNER JOIN Roles R ON R.ID = UR.RoleID
INNER JOIN Permissions P ON P.RoleID = R.ID
GO
Then, map it according in you User.hbm.xml file:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="User" table="Users">
<id name="Id" column="ID">
<generator class="identity"/>
</id>
<property name="Name" length="100"/>
<property name="UserName" length="10" not-null="true"/>
<property name="Password" length="10" not-null="true"/>
<property name="IsActive" not-null="true"/>
<list name="Roles" table="UsersRoles" access="private-property" lazy="true">
<key column="UserID" foreign-key="FK_UR_U"/>
<list-index column="UserID"/>
<many-to-many class="Role" column="RoleID" />
</list>
<!-- Here mapping Permissions granted to User. -->
<list name="Permissions" table="udvUsersPermissions" lazy="true">
<key column="UserID"/>
<list-index column="MenuItemKey"/>
<many-to-many column="ID" class="Permission"/>
</list>
</class>
</hibernate-mapping>
And here, I will let you know about the subselect solution, in case it works the way I didn't expect it to.
<list name="Permissions" lazy="true">
<subselect> <!-- see section 7.6, Chapter 7 - Association mappings -->
select U.ID, P.ID, P.MenuItemKey
from Users U
inner join UsersRoles UR ON UR.UserID = U.ID
inner join Roles R ON R.ID = UR.RoleID
inner join Permissions P ON P.RoleID = R.ID
group by U.ID, P.ID, P.MenuItemKey
order by P.MenuItemKey
</subselect>
<key column="U.ID"/>
<list-index column="P.MenuItemKey"/>
<many-to-many class="Permission" column="P.ID"/>
</list>
Now, I hope I brought enough details so that it helps you achieve what you want to do or either get on track. =)
I'm currently trying to solve a problem. I have a class table inheritance aka table-er-subclass (one main table + several others with additional data). In my app both base object instances and extended objects can exist. Now I want to be able to sometimes fetch only those base objects and sometimes both types. A simple example (both classes are mapped with all of their properties)
public class Base
{
public in ID {get; set;}
public string Something {get; set;}
}
public class Extended : Base
{
public bool NewProp{get; set;}
}
now running hql query "from Base" would fetch both Base and Extedned objects. Is there any way to restrict such behavior to fetch only Base objects?
with HQL you should be able to use the "class" special property:
from Base b where b.class=Base
another approach could be to use plain SQL where you have greater control of what you retrieve.
Anyway check the (N)Hibernate docs.
That's the mapping for the above sample (if there's any error forgive me it must be a typo cause the sample runs)
<?xml version="1.0" encoding="utf-8"?>
<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="Base, Test"
table="base">
<id name="ID"
access="property"
column="ID"
type="Int64"
unsaved-value="0">
<generator class="sequence">
<param name="sequence">base_id_seq</param>
</generator>
</id>
<property name="Something"
access="property"
type="String">
<column name="somethin"/>
<joined-subclass name="Extended, Test"
table="extended"
schema="extended">
<key column="id" />
<property name="NewProp"
access="property"
type="Boolean">
<column name="newProp"/>
</property>
</joined-subclass>
</class>
</hibernate-mapping>