Castle Demo App: Active Record Relations

For this part, I'm going to use NHibernate.Generics, which is an extention to NHibernate that allows to use generic collections for relationship between classes. You need to download it from here and add a reference to:

  • NHibernate.Generics.dll
  • Iesi.Collections.dll

Okay, so we have a User, now we need a Bug class, so we can store the information about our bugs. I'm going to skip the parts that I've already covered and move to the new stuff. Here is how the Bug class looks like:

There are three interesting properties here, BugStatus, Text and AssignedTo. Let's go over each one in turn.

BugStatus is an enum defined like this:

public enum BugStatus : byte

{

    Opened              = 1,

    Duplicated          = 2,

    CouldNotRepreduce   = 3,

    Fixed               = 4,

    WontFix             = 5 //Urgh!

}

As you can see, it's just a normal enum (with the backing store specified as a byte). You can also see that I've some strong feeling about Won't Fix bugs :-) Here is how I defined the Status property on the Bug class:

    [Property]

    public BugStatus Status

    {

        get { return _status; }

        set { _status = value; }

    }

It's just a normal property, with the [Property] attribute to decorate it and tell Active Record that it should save it to the database. No special handling required for enums.

The second interesting property is Text, since we might want to store large amount of text there. The issue in this case is a bug in ActiveRecord/NHibernate (which will probably be fixed in the next release) where text larger than 4,000 character is being silently truncated. The fix for this is explicitly stating that this property may contain large amount of text. All we need to do is specify the column type as StringClob, like this:

    [Property(ColumnType="StringClob")]

    public string Text

    {

        get { return _text; }

        set { _text = value; }

    }

Now we get to the AssignedTo property, which is a fair bit more interesting. I'm following the Hot Potato Bug Tracking phylosophy, which basically says that a bug always has someone assigned to take care of it. This mean that there is a connection between a Bug and a User. How do we specify this?

    [BelongsTo]

    public User AssignedTo

    {

        get { return _assignedTo.Value; }

        set { _assignedTo.Value = value; }

    }

This requires some explanation, in general, just specifying that this Bug [BelongsTo] a user is enough. But right now NHibernate doesn't support generic collections right now, in order to support that you need to use NHibernate.Generics, which provides this functionality. The _assignedTo variable isn't of type User, it's actually defined as:

    EntityRef<User> _assignedTo;

 

    public Bug()

    {

        _assignedTo = new EntityRef<User>(

            delegate(User user) { user.AssignedBugs.Add(this); },

            delegate(User user) { user.AssignedBugs.Remove(this); }

            );

    }

What this basically says is that _assignedTo is a reference to another class, and the part in the constructor we define the actions that will happen when the reference is changed. In this case, we add or remove the current instance to the Bugs collection of the user. This ensure that the right thing happens both in the database and in our objects. In general, you can just use it as is, and don't need to poke inside. If you wish to understand deeper, I suggest you look in NHibernate.Generics documentation.

But what about the other side? If I've a user, how do I know what bugs are assigned to it? Well, that turns out to be fairly simple as well, we need to add the following to the User class:

    EntitySet<Bug> _assignedBugs;

 

    [HasMany(typeof(Bug), ColumnKey="AssignedTo",RelationType=RelationType.Set,

        CustomAccess = "NHibernate.Generics.GenericAccessor, NHibernate.Generics")]

    public ICollection<Bug> AssignedBugs

    {

        get { return _assignedBugs; }

    }

 

    public User()

    {

        _assignedBugs = new EntitySet<Bug>(

            delegate(Bug bug) { bug.AssignedTo = this; },

            delegate(Bug bug) { bug.AssignedTo = null; }

            );

    }

What does this mean? Well, here we tell Active Record that a user has many assigned bugs, that it should threat them as a set and how to locate them in the database. Again, we use the NHibernate.Generics addin to get generic collection support. And we define actions that will happen when a bug is assigned to / from the user. The use of NHibernate.Generics add a little of complication to the attributes, but you get generic collection and smart behavior in return, so I think that it is more than worth it. :-)

Okay, that was quite a bit to chew on, let's finish with a couple of notes on the Bugs tables:

CREATE TABLE [dbo].[Bugs](

      [Id] [int] IDENTITY(1,1) NOT NULL,

      [Title] [nvarchar](255) NOT NULL,

      [Text] [nvarchar](max) NOT NULL,

      [Status] [tinyint] NOT NULL,

      [AssignedTo] [int] NOT NULL,

 CONSTRAINT [PK_Bugs] PRIMARY KEY CLUSTERED

(

      [Id] ASC

))

GO

ALTER TABLE [dbo].[Bugs]  WITH CHECK ADD  CONSTRAINT [FK_Bugs_Users] FOREIGN KEY([AssignedTo])

REFERENCES [dbo].[Users] ([Id])

GO

The only thing of note here is the Status column, which is defined as a tinyint, as you recall, a Bug's Status is actually an enum whose backing store is byte, so I persisted it as a tinyint, which has the same size. (Both can have ranges of 0 - 255, which is more than enough for most enums.).

Print | posted on Friday, February 17, 2006 11:17 PM

Feedback


  2/25/2006 5:26 PM ben

hi
question for line:
[Text] [nvarchar](max) NOT NULL,

-&gt; max: does that work with ms-sql?


Gravatar

#  2/25/2006 9:14 PM Ayende Rahien

Yes, it is a MS SQL Server thingie.


#  9/18/2006 8:18 AM Ken Egozi

@Ben and @Ayende.

nvarchar(max) is a feature of MS SQL-Server 2005.
In short: it's a datatype representing long strings (over 8000 bytes). As opposed to ntext fields, the data is stored inline with the record, and not in an outer storage like image datatype.

This is possible for the fact that in MS Sql Server 2005, a record and even a field can span over multiple pages.

The 4000 trim by NHibernate is meant to deal with the fact that nvarchar is limited to 4000 chars (8000 bytes). This is not a bug by NHibernate. It will happen even if you'll try to insert a string larger 4000 chars using direct T-SQL to a nvarchar(4000) field.

The need for &quot;StringClob&quot; will be off when NHibernate will have a MS2005Dialect



# Casle Demo App: Midway Summary 2/3/2007 7:49 AM www.ayende.com

Comments have been closed on this topic.