A Brief Introduction to Hibernate for .NET with A Simple Example


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;
}

With this design, a graph of links and nodes can be modeled by instantiating these two classes in which each link has a start node and an end node which can either be different, the same (loop) or null (practically loop or link starting or ending with out a null should not be recommended, but they are allowed here as they are supported by the data structure), and correspondingly each node knows all the links it is connected to and by referring to the properties of the specific link whether the link is outgoing or ingoing. Literally this data structure can model graph of any type.

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);
            }
        }

    }
}

While the mapping defined in xml file looks like the following (Note that all NHibernate map definition files are suffixed by hbm.xml with filename exactly the same as the CSharp module name of the primary class the mapping is defined upon, and the mapping file should always be placed in the starting application project folder with its Build Action always set to 'Embedded Resource' even if the classes are defined in other projects for instance in a separate class library; an application configuration file for the starting application project is also mandatory for describing the running environment and specifying parameters and attributes related to underlying database)

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;   
        }
    }
}

I suppose the a separate node collection property has to be set up to wrap up the node array instead of exposing that array directly to the NHibernate layer (similar to the upstream and downstream links defined in the class above), as it is required by NHibernate (as is with Hibernate for java as well) that entity of collection type (set, map, list or whatever) has to be got and set by getter and setter accessors (in .NET which are properties) as a whole. I also suppose partly based on the way it behaves and the freedom such definition allows, (N)Hibernate may assume the collection it retrieves from getter accessor is the original and only copy of the data held by the class, which means however many times it invokes the accessor, the collection returned should always be the same; moreover, (N)Hibernate may also change the values of the entries in the collection retrieved by the accessor and assume that changes are reflected in all other references (to the same copy) it has retrieved from other invocations of the accessor. Nevertheless, the way modern OO languages like Java and C# work determines that (N)Hibernate can never assume that all references of collections it obtained from getter can be updated after it sets a new collection by calling the setter.

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>
 
Node.hbm.xml:

...

    <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.

你可能感兴趣的:(Hibernate)