... Home Contact

Krzysztof Koźmic's blog

You're doing it wrong.


Show appreciation: My Amazon.com Wish List

Me@Twitter

    Currently reading

    Article Categories

    Archives

    Post Categories

    MyPersonal

    Syndication:

    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


    Feedback

    Gravatar

    # re: Learning in the Open: II – first relation and more ActiveRecord
    Thilak Nathen | 2/2/2010 8:36 PM

    hbm2dll auto quoting would be a much elegant solution.
    Gravatar

    # re: Learning in the Open: II – first relation and more ActiveRecord
    Krzysztof Koźmic | 2/2/2010 8:57 PM

    @Thilak Nathen,

    Be so kind to read the entire post before commenting.
    Gravatar

    # re: Learning in the Open: II – first relation and more ActiveRecord
    Thilak Nathen | 2/2/2010 11:10 PM

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

    # re: Learning in the Open: II – first relation and more ActiveRecord
    Fred Legrain | 2/3/2010 10:56 AM

    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
    Gravatar

    # re: Learning in the Open: II – first relation and more ActiveRecord
    Krzysztof Koźmic | 2/3/2010 12:43 PM

    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.

    Leave Your Comment

    Title*
    Name*
    Email (never displayed)
     (will show your gravatar)
    Url
    Comment*

    Please add 4 and 6 and type the answer here:

    Preview Your Comment.