I have a base class for content items in a CMS I'm building. It's currently marked abstract because I only want derived classes to be instantiated. Derived classes like BlogPost, Article, Photo, etc. are set up as a joined subclass to my ContentBase class in nHibernate.
I'm trying to set up a many-to-many mapping between this class and a Tag class. I want to have a collection of Tags on the ContentBase class, and a collection of ContentBase items on the tag class.
Will nHibernate allow me to map the abstract ContentBase class as a collection on the Tag class? I'm assuming not since it wouldn't be able to instantiate any instances of this class when reconstituting a Tag entity from the db. I really don't want to have to have to use a collection of content items per type (e.g. TaggedBlogPosts, TaggedArticles, etc.) on the Tag class.
The whole reason I'm doing this is because logically, a content item can have many tags, and 1 tag can belong to multiple content items. in order for nHibernate to manage the relationships for me in a mapping table, I believe I have to set up a many-to-many association and add the Tag to the ContentBase.Tags collection and then the content item to the Tags.TaggedContentItems collection before the mapping table entry is created in nHibernate.
Here are my mappings for reference:
<class name="CMS.Core.Model.Tag,CMS.Core" table="bp_Tags">
<id column="TagName" name="TagName" type="String" unsaved-value="">
<generator class="assigned" />
</id>
<bag name="_taggedContentList" table="bp_Tags_Mappings" inverse="true" cascade="save-update" lazy="true">
<key column="TagName" />
<many-to-many class="CMS.Core.Model.ContentBase,CMS.Core" column="Target_Id" />
</bag>
</class>
<class name="CMS.Core.Model.ContentBase,CMS.Core" table="bp_Content">
<id name="Id" column="Id" type="Int32" unsaved-value="0">
<generator class="native"></generator>
</id>
<property name="SubmittedBy" column="SubmittedBy" type="string" length="256" not-null="true" />
<property name="SubmittedDate" column="SubmittedDate" type="datetime" not-null="true" />
<property name="PublishDate" column="PublishDate" type="datetime" not-null="true" />
<property name="State" column="State" type="CMS.Core.Model.ContentStates,CMS.Core" not-null="true" />
<property name="ContentType" column="ContentType" type="CMS.Core.Model.ContentTypes,CMS.Core" not-null="true" />
<bag name="_tagsList" table="bp_Tags_Mappings" lazy="false" cascade="save-update">
<key column="Target_Id" />
<many-to-many class="CMS.Core.Model.Tag,CMS.Core" column="TagName" lazy="false" />
</bag>
...
<joined-subclass name="CMS.Core.Model.BlogPost,CMS.Core" table="bp_Content_BlogPosts" >
<key column="Id" />
<property name="Body" type="string" column="Body" />
<property name="Title" type="string" column="Title" />
</joined-subclass>
...
I would also assume that Hibernate would need to instantiate the base class, which makes sense, as there is data directly linked to it in the DB.
It looks like your data entities are in a separate assembly from your main application. The point of not instantiating the base is from a business point of view, so if you make the constructor internal to the Core assembly, does that accomplish what you want? If not, it may be helpful to ask yourself: who am I protecting this functionality from?
NHibernate should allow you to map the abstract class as a collection as long as you map the abstract class itself (it looks like you have).
An alternative is to change your strategy to the Table-per-class-hierachy approach. This puts all your content into a single table with a discriminator column to define the type of content. NHibernate knows how to materialize each content type based on the discriminator. The downside is that:
Each unique property of the concrete classes has to be of type nullable.
This table can get unwieldy if there are a lot of properties specific to each concrete class.
The table-per-class-hierachy is the preferred default mapping strategy (per NHibernate in Action). I would start with the approach and modify it to table-per-subclass when the need is certain.
A whole separate solution would be keep the mapping as is and rely on query to get the content by tag without worrying about the tags keeping a reference to a collection of content.
Related
Having trouble with implementing second level cache in Nhibernate. I have a class mapped as follows:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Data" namespace="Data">
<class name="Account" table="Accounts" lazy="false">
<cache region="Standard" usage="read-write" include="all"/>
<id column="ID" name="ID">
<generator class="assigned" />
</id>
<version name="VersionStamp" column="VersionStamp" type="integer" unsaved-value="0" />
<property name="Name" not-null="true" />
<property name="Type" not-null="true" />
<property name="ClientID" not-null="true" insert="false" update="false" />
<property name="DateCreated" not-null="true" type="UtcDateTime" />
<property name="LastUpdatedDate" not-null="true" type="UtcDateTime" />
<property name="IsActive" not-null="true" />
<many-to-one name="Client" class="Client" column="ClientID" not-found="exception" not-null="true" />
</class>
</hibernate-mapping>
The property "ClientID" is a foreign key into the Clients table and the Client many-to-one property uses it to look up the associated client object.
When I add a new Account, I look up the Client object from the database with a Session.Get and assign it to my Account object's Client property. Behind the scenes, this also automatically populates the ClientID property when the object is written to the database, and the ID is correctly stored in the database. When I retrieve the Account object from the database by ID using Session.Get, all the fields are populated correctly when the object is retrieved.
However, when I implement the second level cache using the settings shown above, the ClientID property is NOT populated when the Account object is retrieved using Session.Get, but the Client property is populated correctly. Is there some reason why this will not work with second level cache? Or have I done something wrong in my mapping/configuration?
For now I am just using SysCache as my caching provider, and both query and second level cache are turned on. The Client class mapping contains a corresponding one-to-many property for the Accounts.
I like the convenience of having the ClientID property on my Account class, so that I can read it without using the Client property, and it seems to work fine without caching.
Thanks for any help.
Rich
I tried to reproduce your situation locally. With your mapping (used the same as the snippet above) I was able to get incorrect behaviour only on UPDATE. In that case, the ClientID was cached, and while the Client reference was changed, the ClientID remained unchanged. In other cases caching was working as expected.
The solution is to change the mapping. The below suggested mapping is the most suitable for read-only properties like ClientID. (I am using that approach as well).
<property name="ClientID" formula="[ClientId]"
not-null="true" insert="false" update="false" />
So the trick is in the formula mapping instead of Column (the default when none is provided)
I have a true one to one mapping. But I would like to use lazy loading(load on demand).
I have Class Person with a association with Class Address. The mapping looks like this..
PERSON
<one-to-one name="address" class="Person" cascade="all-delete-orphan" access="field">
ADDRESS
<class name="Address" table="Address" lazy="true">
<id name="id" column="addressId" type="Int32" access="field">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person" class="Address" constrained="true" access="field" />
Does anyone see something wrong with this? How do I enable proxy/lazy loading for address?
Thanks
Heres a good discussion on the topic http://ayende.com/Blog/archive/2007/05/10/NHibernate-onetoone.aspx
Crucial quote: "In other words, one-to-one cannot be lazily loaded, which is one of the reasons why it is recommended to use two many-to-one instead."
Also see https://www.hibernate.org/162.html and NHibernate: how to enable lazy loading on one-to-one mapping
It is possible to specify an arbitrary SQL where clause for collection mappings. For example:
<map name="myEntity" where="foo = 1" />
However if the column name is ambiguous for some reason, the sql fails. For example, this can occur if you are trying to use joins for example.
Given that the table aliases are automatically generated, you can't qualify the column name. This makes the feature seem rather silly. Does anyone know if there is a work around?
NHibernate should figure out the correct alias for the property you are referencing. Is foo a mapped property of the item entity type (the item type that is in the map collection) ?
For example this works:
<class name="Category" table="Category">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Name" not-null="true" length="255" />
<bag name="ProductList" table="Product" cascade="none" where="Name like '%test%'" fetch="join">
<key column="CategoryId" />
<one-to-many class="Product" />
</bag>
</class>
There is a property on both Category and the Product class named "Name" but nhibernate will in this case use the on defined on the Product class.
Currently I have the following classes:
class Article with properties id, title and body
class Question : Article with an extra PostedBy property
Then I have a table called Article with the above properties and a table called questions with an ID a foreign key articleID and a PostedBy. Both are in different schemas
I would like to know how are my mappings going to look to represent this relation. Both classes are in different assemblies and i would be very reluctant to put Question logic in Article class/mapping and its assembly.
NHibernate supports three basic inheritance strategies.
table per class hierarchy
table per subclass
table per concrete class
It sounds like you are looking for the table per subclass strategy as you have a table for your Article class and another table for the extra properties on the Question subclass. The mapping might looks something like this:
<class name="Article" table="Article">
<id name="Id" type="Int64" column="ArticleId">
<generator class="native"/>
</id>
<property name="Title" column="Title"/>
<property name="Body" column="Body"/>
...
<joined-subclass name="Question" table="Question">
<key column="ArticleId"/>
<property name="PostedBy" column="PostedBy"/>
...
</joined-subclass>
</class>
However, this doesn't meet your desire to keep the mappings entirely separate. You could have entirely separate mappings, but this might have some side effects as allowing Question to be loaded as a plain Article instead of a Question. With separate mapping the Article class would be straight-forward as expected. The Question class would include a join to access the properties stored in the Article table.
<class name="Article" table="Article">
<id name="Id" type="Int64" column="ArticleId">
<generator class="native"/>
</id>
<property name="Title" column="Title"/>
<property name="Body" column="Body"/>
...
</class>
<class name="Question" table="Question">
<id name="Id" type="Int64" column="QuestionId">
<generator class="native"/>
</id>
<property name="PostedBy" column="PostedBy"/>
...
<join table="Article">
<key column="ArticleId"/>
<property name="Title" column="Title"/>
<property name="Body" column="Body"/>
</join>
</class>
I'm using nHibernate to map an object very similar to .NET's System.Web.SiteMapNode. In order to keep my object similar to this .NET object I would like to have it contain a ParentNode, PreviousSibling, NextSibling, and ChildNodes complex properties.
The table looks somewhat like this and is open to be changed:
ID (int)
Title (string)
Description (string)
Key (string)
ParentNodeId (int)
OrdinalPosition (int)
ReadOnly (bool)
Url (string)
I may have some other properties that are not needed to mimic the .NET SiteMapNode object (like an isExternal bool), but I think those are inconsequential to this question.
My current mapping looks like this:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="AthletesCafe.Core.Domain.System.SiteMap" assembly="AthletesCafe.Core">
<class name="SiteMapNode" table="SiteMapNode" lazy="true" >
<id name="ID" type="Int32" unsaved-value="0">
<column name="ID" not-null="true" unique="true" index="PK_SiteMapNode"/>
<generator class="identity" />
</id>
<property name="Title" column="Title" type="String" length="255" not-null="true" />
<property name="Description" column="Description" type="String" not-null="false" />
<property name="Url" column="Description" type="String" not-null="true" />
<property name="SiteMapKey" column="SiteMapKey" type="String" not-null="true" length="255" />
<property name="OrdinalPosition" column="OrdinalPosition" type="Int32" not-null="true" />
<property name="ReadOnly" column="ReadOnly" not-null="true" type="System.Boolean" />
<property name="IsExternal" column="IsExternal" not-null="true" type="System.Boolean" />
<many-to-one name="ParentNode" column="ParentNodeId" class="AthletesCafe.Core.Domain.System.SiteMap.SiteMapNode, AthletesCafe.Core"
access="field.pascalcase-underscore" not-null="false" />
<many-to-one name="PreviousNode" column="ParentNodeId" class="EatMyTrainer.Core.Domain.SiteMap.SiteMapNode, EatMyTrainer.Core" not-null="false" /></hibernate-mapping>
The ParentNode mapping is easy as it should be just a simple many-to-one mapping. This is the code I have for it (untested, but I believe it to be correct):
<many-to-one name="ParentNode" column="ParentNodeId" class="AthletesCafe.Core.Domain.System.SiteMap.SiteMapNode, AthletesCafe.Core"
access="field.pascalcase-underscore" not-null="false" />
The mapping for the child nodes should just be a simple bag which will bring back all SiteMapNode objects that have the ParentNodeId equal to the current ID. I haven't written this bag yet, but I believe it to be not such a big deal.
The issue that I cannot seem to resolve is how to do the Next/Previous Sibling properties. This objects can be derived from the following formula for each node:
PreviousSibling: Has the same ParentNode (ParentNodeId) as the current object and its OrdinalPosition should be one less than the current object's OrdinalPosition.
NextSibling: Has the same ParentNode (ParentNodeId) as the current object and its OrdinalPosition should be one more than the current object's OrdinalPosition.
I think this is achievable through the formual attribute on a many-to-one mapping. Is this possible? I haven't found a good example of how this works.
I don't think what you're asking for is strictly possible (although I would be very interested to see the solution if it is). There would be a relatively simple workaround, but NHibernate does not support bidirectional one-to-many mappings with indexed collections on the many end.
The only thing that comes to mind is a bit ugly: have the parent object keep its own index map (keyed off the OrdinalPosition) to each child object. On the child do something like:
public SiteMapNode NextSibling()
{
return this.Parent.NextSibling(this);
}
I believe Stuart is correct in this situation. It is impossible to do for the many-to-one mapping. If NHibernate provided a way to do where clausing on this mapping then I may have a chance.
Another possible solution although inefficient is to create a bag that uses a field setter. The public property that would be Next/Previous setting would still return an object reference (as opposed to an enumerable). In the getter it would just reference the first position of the enumerable in the field. Lazy loading would be ideal because NHibernate wouldn't be able to load this object in one get with the initial load of the object. You would have a penalty for accessing this object every time.
I guess both solutions have a similar penalty.