Click here to Skip to main content
13,256,716 members (52,073 online)
Click here to Skip to main content
Add your own
alternative version

Stats

15.3K views
21 bookmarked
Posted 31 Mar 2009

NHibernate and Complex Types for Native Keys

, 3 Jun 2009
Rate this:
Please Sign up or sign in to vote.
Learn how to use complex classes as primary (not composite!) keys with NHibernate

Primary keys may take several forms: guid, integer, long, etc. It often makes sense to represent the key field as a POCO rather than its native type.

Why would I do this?

First, it hides the implementation. I don't have to worry about whether the key is an integer or a long, instead, I interact with my complex type.

It allows me to do some more interesting things with a key, too. For example, let's say I did choose to use identity fields (integers) so one implementation of my key is a "CoreKey" that contains an "ID" of type Integer.

It is common to see people use 0 or -1 as a "null" or "uninitialized" value for this type of key. With my class, I can do a little more and define it like this:

/// <span class="code-SummaryComment"><summary>
</span>///     CoreKey provides the unique identifier for classes in the system
/// <span class="code-SummaryComment"></summary>
</span>[Serializable]
public class CoreKey : IComparable<CoreKey>, IEquatable<CoreKey>, ICloneable 
{
    /// <span class="code-SummaryComment"><summary>
</span>    ///     Represents value when not initialized
    /// <span class="code-SummaryComment"></summary>
</span>    private const int NOT_INITIALIZED = -1;
    
    /// <span class="code-SummaryComment"><summary>
</span>    ///     Unique identifier
    /// <span class="code-SummaryComment"></summary>
</span>    public int ID { get; set; }
    
    /// <span class="code-SummaryComment"><summary>
</span>    ///     Represents an "empty" or unitialized key
    /// <span class="code-SummaryComment"></summary>
</span>    public static CoreKey Empty
    {
        get
        {
            return new CoreKey(NOT_INITIALIZED); 
        }
    }
    
    /// <span class="code-SummaryComment"><summary>
</span>    ///     Default -1
    /// <span class="code-SummaryComment"></summary>
</span>    public CoreKey()
    {
        ID = NOT_INITIALIZED;
    }
    
    /// <span class="code-SummaryComment"><summary>
</span>    ///     Initialize with id
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="id">The unique identifier</param>
</span>    public CoreKey(int id)
    {
        ID = id;
        _Initialize();
    }
    
    /// <span class="code-SummaryComment"><summary>
</span>    ///     Initializes properties to valid values
    /// <span class="code-SummaryComment"></summary>
</span>    private void _Initialize()
    {
        if (ID <= 0)
        {
            ID = NOT_INITIALIZED;
        }
    }
    
    /// <span class="code-SummaryComment"><summary>
</span>    ///     Tests to see whether the key is initialized
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="coreKey">The instance of <seealso cref="CoreKey"/> to test</param>
</span>    /// <span class="code-SummaryComment"><returns>True if null or empty</returns>
</span>    public static bool IsNullOrEmpty(CoreKey coreKey)
    {
        return coreKey == null || coreKey.ID.Equals(NOT_INITIALIZED);
    }
    
    /// <span class="code-SummaryComment"><summary>
</span>    ///     Attempt to parse a value to the key. Returns true if the
    ///     attempt succeeded.
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="value">The value to try to case as a key</param>
</span>    /// <span class="code-SummaryComment"><param name="coreKey">A <seealso cref="CoreKey"/>
</span>    /// instance to case the value to<span class="code-SummaryComment"></param>
</span>    /// <span class="code-SummaryComment"><returns>True if the cast succeeded</returns>
</span>    public static bool TryParse(string value, out CoreKey coreKey)
    {
        bool retVal = false; 
        
        int id; 
        if (int.TryParse(value, out id))
        {
            coreKey = new CoreKey(id);
            retVal = true;
        }
        else
        {
            coreKey = null;
        }
        
        return retVal; 
    }

Notice that when I create a key (CoreKey key = new CoreKey()), it is immediately initialized to CoreKey.Empty, a special value I can check for. Instead of "if id < 1", I can use "CoreKey.IsNullOrEmpty" and I can even TryParse this.

This is all and well until you try to wire the class into NHibernate. The classic example for an NHibernate mapping file goes something like this:

<?xml version="1.0" encoding="utf-8"?>
<nhibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="MyApp.Widget,MyApp" table="widgets">
    <id name="Id" column="WidgetId">
      <generator class="native"/>
    </id>
    <property name="WidgetName" column="name" type="String" length="40"/>
  </class>
</nhibernate-mapping>

That's all good and well, but our widget uses a CoreKey, right? So what now? Try to reference the type, and you start to have problems:

<id name="Id" column="WidgetId" type="Framework.CoreKey,MyApp"/>

Of course, this will blow up. Why? Because the type coming from the database is an integer, and our CoreKey is NOT an integer. So what do we do?

This seems like it should be a simple problem to solve, but I searched far and wide to find a solution. I was trying components and composite keys and everything in between. I finally found an example that mentioned using the special interface IUserType supplied by NHibernate to, well, create our own types.

Unfortunately, the example included referencing NHibernate, my "persistence engine" or data layer, and then applying the interface to my domain object ("business entity"). Can you imagine dirtying your entity layer with ugly references to the implementation of your data store? Shouldn't, couldn't, and wouldn't happen.

Fortunately, the solution was rather simple and straightforward. Thanks to C# and inheritance, my domain objects only have to know about a CoreKey. However, my data layer can interact with something smarter, something that implements the IUserType interface AND talks the language of CoreKey.

Introducing CoreKeyProxy, an object defined in the data layer of the application. Here is the meat ... I've edited out comments and some of the other methods, and included the key items that map the database values to the custom type:

public class CoreKeyProxy : CoreKey, IUserType
{
    public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
    {
        object r = rs[names[0]];
        if (r == DBNull.Value)
            return null;
        return new CoreKey((int)r);  
    }
    
    public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
    {
        IDataParameter parameter = (IDataParameter)cmd.Parameters[index];
        parameter.Value = value == null ? null : (object)((CoreKey)value).ID;  
    }
    
    public Type ReturnedType
    {
        get { return typeof(CoreKey); }
    }
    
    public global::NHibernate.SqlTypes.SqlType[] SqlTypes
    {
        get { return new[] { new SqlType(DbType.Int32) }; }
    }
}

Now I simply define my id as type="CoreKeyProxy" and voila! I'm able to integrate it seamless with my key class.

Jeremy Likness

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jeremy Likness
Instructor / Trainer Microsoft
United States United States
Note: articles posted here are independently written and do not represent endorsements nor reflect the views of my employer.

Jeremy Likness is a Cloud Developer Advocate for Azure at Microsoft. Jeremy has spent two decades building enterprise software with a focus on line of business web applications. Jeremy is the author of several highly acclaimed technical books including Designing Silverlight Business Applications and Programming the Windows Runtime by Example. He has given hundreds of technical presentations during his career as a professional developer. In his free time, Jeremy likes to CrossFit, hike, and maintain a 100% plant-based diet.

Jeremy's roles as business owner, technology executive and hands-on developer provided unique opportunities to directly impact the bottom line of multiple businesses by helping them grow and increase their organizational capacity while improving operational efficiency. He has worked with several initially small companies like Manhattan Associates and AirWatch before they grew large and experienced their transition from good to great while helping direct vision and strategy to embrace changing technology and markets. Jeremy is capable of quickly adapting to new paradigms and helps technology teams endure change by providing strong leadership, working with team members “in the trenches” and mentoring them in the soft skills that are key for engineers to bridge the gap between business and technology.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.171114.1 | Last Updated 3 Jun 2009
Article Copyright 2009 by Jeremy Likness
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid