To continue the topic of why Java still there since the advent of .NET 2.0; Recently as required by the work, I had to have a look at the Hibernate technology. And as the projects we are on are either .NET or native Visual C++ based, if we need to do any data mapping, we have to find technologies that are language neutral or adapted to these enviroments.
NHibernate is the version of Hibernate specially made for .NET framework. the official website for NHibernate is: http://nhforge.org/Default.aspx
NHibernate fully embraces the concept of object-relational mapping (ORM) Hibernate does and implements most relevant functionalities. Although Hibernate orginates in Java world and is much more actively oriented to Java development, NHibernate by taking advantange of some excellent features of .NET framework provides a even more friendly set of programming interfaces and thus better user experience, which is not surprising. Below is an example of how NHibernate can be used in a .NET data persistence involved project.
In this example, the object oriented model will involve entities briefly described in C# style pseudo code as follows:
class Link { Node StartNode; Node EndNode; } class Node { List<Link> Links; }
However when it comes to mapping and persistence for the data of the graph with Hibernate, a few detailed technical issues need to be addressed.
At the first glance, we can see the relationship between links and nodes can be modeled as a pair of one-to-many mappings. However the real thing is not so straightforward as the problem appears. As the Node class is implemented this way, there's only a single list that stores all links associated with the node, rather thant separate lists on the 'one' side to accordingly represent the mapping from the node to the list of links either it is a start node for or it is an end node for.
One way of solving the problem is changing the definition of the Node class so that links of two different types are kept in separate lists, e.g. UpstreamLinks and DownstreamLinks, and thus a bidirectional one-to-many mapping from StartNode to DownstreamLinks and another from EndNode to UpstreamLinks can be established. The change causes some overheads in terms of memory, however the adaptation for application layer is quite simple. The following code gives an idea of it.
class Node { List<Link> _downstreamLinks = new List<Link>(); List<Link> _upstreamLinks = new List<Link>(); public ICollection<Link> DownstreamLinks { get { return _downstreamLinks; } set { _downstreamLinks.Clear(); foreach (Link link in value) _downstreamLinks.Add(link); } } public ICollection<Link> UpstreamLinks { ... } // implemented similarly public IEnumerable<Link> Links { get { foreach (Link link in DownstreamLinks) yield return link; foreach (Link link in UpstreamLinks) yield return link; } set { DownstreamLinks.Clear(); UpStreamLinks.Clear(); foreach (Link link in value) { if (link.StartNode == this) DownstreamLinks.Add(link); if (link.EndNode == this) UpstreamLinks.Add(link); } } } }
Node.hbm.xml:
<hibernate-mapping ...> <class table="Node" name="NS.Node, NS"> ... <set name="DownstreamLinks"> <key column="StartNode"/> <one-to-many class="NS.Link, NS"> </set> <set name=UpstreamLinks"> <key column="EndNode"/> <one-to-many class="NS.Link, NS"> </set> </class> </hibernate-mapping>Link.hbm.xml:
<hibernate-mapping ...> <class table="Links" name="NS.Links, NS"> ... <many-to-one name="StartNode" class="NS.Links" column="StartNode"/> <many-to-one name="EndNode" class="NS.Links" column="EndNode"/> </class> </hibernate-mapping>
Two tables should be created named Node and Link
Columns of Table Node:
Id - of type char(32) (depending on what type of Identifier is needed and what Id generator is used)
Name - of type e.g. nvarchar(50)
PosX - of type float which corresponds to double in .NET
PosY - of type float
Columns of Table Link:
Id - of type char(32) or whatever
Name - of string type like nvarchar(50)
StartNode of the same type as that of Id for Table Node
EndNode of the same type as above
However one of the downsides of this design is, apart from certain overheads, that application developer needs to be careful about using the methods for creating the nodes and links and getting them connected, since there may be some restrictions on the order in which these methods are potentially invoked, e.g. the relation of a node to the link may need to be determined by setting either of the two properties of the link before the link can be added to the right list by a uniform function call in such a form like AddLink(Link link) which without the node being attached to the link by one of its property is unable to tell which list to add the link to.
So one workaround might be making some changes to the Link class so that a bidirctional many-to-many can be used:
class Link { Node[] _nodes = new Node[2]; public Node StartNode { get { return Nodes[0]; } set { Nodes[1] = value; } } public Node EndNode { ... } public ICollection<Node> Nodes { get { return _nodes; } set { i = 0; foreach(Node node in value) _nodes[i++] = node; } } }
The XML code snippets for many-to-many mapping may look like the following (Note the collection is represented by 'bag' element in NHibernate, another option being 'set' which I guess doesn't allow duplicate items or keep the order):
Link.hbm.xml:
... <bag name="Nodes" table="Node_Link" cascade="all"> <key column="LinkId" /> <many-to-many column="NodeId" class="GrapheneNetworkLite.Node, GrapheneNetworkLite"/> </bag>
... <bag name="ConnectedLinks" table="Node_Link" inverse="true" cascade="all"> <key column="NodeId" /> <many-to-many column="LinkId" class="GrapheneNetworkLite.Link, GrapheneNetworkLite"/> </bag>
Note that one of the two directions of the mapping has to be marked with 'inverse' perperty set to 'true', so that the two directions can be both differentiated and correlated.
We can get rid of the StartNode and EndNode columns from the Link Table with an extra table named Node_Link that underpins the mapping defined as follows
LinkId - of Id type for Link
NodeId - of Id type for Node
The example above gives a quick glimpse of the NHibernate technology, however good news for C# programmer is far more than that. There is an extendion called Fluent NHibernate which makes use of .NET language features to support mapping definition by source code which is compiler checking enabled and more flexible.