Learning in the Open: II – first relation and more ActiveRecord

It took a little longer than I planned but here we go again. In the meantime ActiveRecord 2.1 was released, and soon after that a minor update bringing one cool big feature. From now on we’ll be working on version 2.1.2. Picking up from where we left off last time. We have a user entity. Since we’re building a website where users can publish benchmark results, we’ll create now a benchmark entity, and create a relation between these two.

I want to see results!

Let’s start by adding an appropriate field to the User class:

private readonly ICollection<BenchmarkResult> benchmarkResults = new HashSet<BenchmarkResult>();

We also create a property:

public IEnumerable<BenchmarkResult> BenchmarkResults

{

    get

    {

        foreach (var result in benchmarkResults)

        {

            yield return result;

        }

    }

}

So far this is just a regular property. To map it as a one-to-many relation we use the HasManyAttribute.

[HasMany(Access = PropertyAccess.FieldCamelcase, 

    Cascade = ManyRelationCascadeEnum.SaveUpdate, 

    RelationType = RelationType.Set,

    Inverse = true)]

public IEnumerable<BenchmarkResult> BenchmarkResults

There’s quite a lot going on here, so let’s go over it piece by piece

  • Access property FieldCamelcase specify we want ActiveRecord (and NHibernate underneath it) to go to the field directly, which makes sense since we’re exposing it as mere enumerable.
  • Cascade specifies that when saving or updating our user, all new and changed benchmark results in the collection should also be appropriately saved or updated.
  • Usually we wouldn’t have to specify type of the relation. Usually it will infer it from the kind of collection we expose, and would use set for ISet, map for IDictionary, bag for ICollection etc. However since we’re exposing only IEnumerable it does not have enough information to decide, that’s why we have to be explicit here.
  • We also specify Inverse property to be true, which basically means that it’s child’s task to maintain the relationship. That also means that child needs to have a reference to the parent.

Let’s now build our BenchmarkResult class.

[ActiveRecord]

public class BenchmarkResult : ActiveRecordLinqBase<BenchmarkResult>

{

    protected BenchmarkResult()

    {

    }

 

    public BenchmarkResult(User user, string benmchmarkName, string computerModel, double score)

    {

        if (user == null)

        {

            throw new ArgumentNullException("user");

        }

        if (benmchmarkName == null)

        {

            throw new ArgumentNullException("benmchmarkName");

        }

        if (computerModel == null)

        {

            throw new ArgumentNullException("computerModel");

        }

 

        User = user;

        BenmchmarkName = benmchmarkName;

        ComputerModel = computerModel;

        Score = score;

    }

}

So far there’s nothing new here. Computer configuration and benchmark will become entities themselves soon, but let’s not get ahead of ourselves.

The only interesting property at this point is the User.

[BelongsTo]

public User User { get; private set; }

It has a BelongsToAttribute to denote it points to another entity (on our case the ‘one’ end of our one-to-many).

Let’s now let our users to actually save benchmark results, and we’re more or less done:

public BenchmarkResult RunBenchmark(string benchmarkName, string computerModel, double score)

{

    var result = new BenchmarkResult(this, benchmarkName, computerModel, score);

    benchmarkResults.Add(result);

    return result;

}

Test

We now have all the logic in place, so let’s build a test:

[Fact]

public void Can_perform_benchmark_runs()

{

    var stefan = new User

    {

        Email = "stefan@gmail.com",

        Name = "Stefan",

        Password = "Super compilcated password!",

        About = "Stefan is a very cool."

    };

    stefan.RunBenchmark("Foo bar!", "AyeMack Pro", 3.2);

    stefan.Save();

 

    var user = User.FindAll().Single();

 

    Assert.NotEmpty(user.BenchmarkResults);

    Assert.Equal(1, user.BenchmarkResults.Count());

 

    var result = user.BenchmarkResults.Single();

 

    Assert.NotNull(result);

    Assert.Equal("Foo bar!", result.BenmchmarkName);

    Assert.Equal("AyeMack Pro", result.ComputerModel);

    Assert.Equal(3.2, result.Score);

}

If we run it now, it will fail. Good news is, that it does not have anything to do directly with our logic. Bad news is, that we have a passing test nonetheless, so let’s have a look at it.

Error

We get “Incorrect syntax near the keyword ‘User’.” error message. Here’s the SQL that was sent to the database:

error_sql

Looks good doesn’t it? Well not – really, User is a SQL Server keyword, as we can’t just use it as identifier – we have to escape it. To do it, we have to specify the column name explicitly, and escape it by enclosing it within two ` characters (located above tab key on my keyboard).

Yes I’m aware of hbm2ddl.keywords auto-quote. However I had some issues getting it to work with ActiveRecord. Any help doing this will be appreciated.

[BelongsTo(Column = "`User`")]

public User User { get; private set; }

Now the test will pass, and the following SQL will be generated:

ok_sql

Now that we have the correct SQL, let’s look at what our schema looks like:

schema_inverse_true

Comments

Thilak Nathen says:

hbm2dll auto quoting would be a much elegant solution.

@Thilak Nathen,

Be so kind to read the entire post before commenting.

Thilak Nathen says:

Ahh, sorry dude… din see your disclaimer. Missed that bit.

I’m still wondering why auto-quoting is not enabled by default for the MsSql200x dialect.

Fred Legrain says:

Thank you for the series!

About escaping the User column name, do you know the reason why this is not handled by the Hibernate SQL dialect?

Cheers,
Frédéric

Frédéric,

actually NHibernate provides this capability. However I had issues using it, which turned out to be probably a bug with NHibernate. Once I know more, I’ll address this in future post.