This is the fifth post of a series started with this post about a MonoRail and ActiveRecord tutorial.
With this post we will finally start to write code for our MonoRail and ActiveRecord tutorial. Here we will go on writing the model classes, and from them we will autogenerate the database schema (alternatively you could first create the database schema and then generate the classes using the generator).
We need to write 5 classes for our 5 models (Album, AlbumGenre, Artist, Genre, Support). As we discussed the convention is to place them under the Models folder of our CastleProject solution.
So let’s create these 5 classes.
1. Album Class
using System; using System.Collections; using Castle.ActiveRecord; using NHibernate.Expression; namespace net.paolocorti.Samples.Castle.Tutorial.Models { /// <summary> /// Album class /// </summary> [ActiveRecord] public class Album : ActiveRecordValidationBase { private int id; private string name; private Int16 year; private Support support; private IList artists = new ArrayList(); private IList genres = new ArrayList(); /// <summary> /// Primary Key /// </summary> [PrimaryKey(PrimaryKeyType.Native)] public int Id { get { return id; } set { id = value; } } /// <summary> /// Name /// </summary> [Property(NotNull = true, Length = 150), ValidateNotEmpty("You need to provide the name of the album!"), ValidateIsUnique("There is already this album!")] public string Name { get { return name; } set { name = value; } } /// <summary> /// Year /// </summary> [Property, ValidateRegExp(@"\d{4}", "You need to provide a valid year!")] //ValidateRegExp(Year) public Int16 Year { get { return year; } set { year = value; } } /// <summary> /// Support /// </summary> [BelongsTo("supportid", Cascade=CascadeEnum.Delete)] public Support Support { get { return support; } set { support = value; } } /// <summary> /// Artists /// </summary> [HasAndBelongsToMany(typeof(Artist), Table = "ArtistAlbum", ColumnKey = "albumid", ColumnRef = "artistid")] public IList Artists { get { return artists; } set { artists = value; } } /// <summary> /// Genres /// </summary> [HasAndBelongsToMany(typeof(Genre), Table = "AlbumGenre", ColumnKey = "albumid", ColumnRef = "genreid")] public IList Genres { get { return genres; } set { genres = value; } } /// <summary> /// Get an HTML view of genre names /// </summary> public string HtmlGenresNames { get { string genresnames = ""; foreach (Genre genre in Genres) { genresnames += genre.Name; AlbumGenre ag = AlbumGenre.Find(this, genre); if (ag.Main && Genres.Count>1) { genresnames += "(*)"; } genresnames += "<br/>"; } return genresnames; } } /// <summary> /// Get an HTML view of artist names /// </summary> public string HtmlArtistsNames { get { string ArtistsNames = ""; foreach (Artist artist in Artists) { ArtistsNames += @"<a href=""/artist/edit.rails?id=" + artist.Id + @""" />" + artist.Name + "<br/>"; } return ArtistsNames; } } /// <summary> /// Find all albums /// </summary> /// <returns></returns> public static Album[] FindAll() { return (Album[])FindAll(typeof(Album), new Order[] { Order.Asc("Name") }); } /// <summary> /// Find album by Id /// </summary> /// <param name="id">Id of Album</param> /// <returns></returns> public static Album FindById(int id) { return (Album)FindByPrimaryKey(typeof(Album), id); } } }
2. AlbumGenre Class
using System; using Castle.ActiveRecord; using NHibernate.Expression; namespace net.paolocorti.Samples.Castle.Tutorial.Models { /// <summary> /// AlbumGenre: it is needed to manage the main attribute in the many2many relationship /// </summary> [ActiveRecord("AlbumGenre")] public class AlbumGenre : ActiveRecordBase { private int _id; private Album _album; private Genre _genre; private bool _main; /// <summary> /// Primary Key /// </summary> [PrimaryKey(PrimaryKeyType.Native)] //Use sequence for Postgres, Native for SQL Server and MySQL public int Id { get { return _id; } set { _id = value; } } /// <summary> /// Album /// </summary> [BelongsTo("albumid")] public Album Album { get { return _album; } set { _album = value; } } /// <summary> /// Genre /// </summary> [BelongsTo("genreid")] public Genre Genre { get { return _genre; } set { _genre = value; } } /// <summary> /// Main genre /// </summary> [Property] public bool Main { get { return _main; } set { _main = value; } } /// <summary> /// Get AlbumGenre of an album /// </summary> /// <param name="album"></param> /// <returns></returns> public static AlbumGenre[] FindByAlbum(Album album) { return (AlbumGenre[])FindAll(typeof(AlbumGenre), Expression.Eq("Album.Id", album.Id)); } /// <summary> /// Get AlbumGenre from an album and a genre /// </summary> /// <param name="album"></param> /// <param name="genre"></param> /// <returns></returns> public static AlbumGenre Find(Album album, Genre genre) { return (AlbumGenre)FindOne(typeof(AlbumGenre), Expression.Eq("Album.Id", album.Id), Expression.Eq("Genre.Id", genre.Id)); } /// <summary> /// Get Main genre of an album /// </summary> /// <param name="album"></param> /// <returns></returns> public static Genre FindMainGenre(Album album) { AlbumGenre[] albumGenres = FindByAlbum(album); Genre mainGenre = null; foreach(AlbumGenre ag in albumGenres) { if (ag.Main == true) { mainGenre = ag.Genre; } } return mainGenre; } } }
3. Artist Class
using System; using System.Collections; using NHibernate.Expression; using Castle.ActiveRecord; namespace net.paolocorti.Samples.Castle.Tutorial.Models { /// <summary> /// Artist /// </summary> [ActiveRecord] public class Artist : ActiveRecordValidationBase { private int id; private string name; private IList albums = new ArrayList(); /// <summary> /// Primary Key /// </summary> [PrimaryKey(PrimaryKeyType.Native)] public int Id { get { return id; } set { id = value; } } /// <summary> /// Name /// </summary> [Property(NotNull = true, Length = 150), ValidateNotEmpty("You need to provide the name of the artist!"), ValidateIsUnique("There is already this artist!")] public string Name { get { return name; } set { name = value; } } /// <summary> /// Find all artists /// </summary> /// <returns></returns> public static Artist[] FindAll() { return (Artist[])FindAll(typeof(Artist), new Order[] { Order.Asc("Name") }); } /// <summary> /// Find an artist by id /// </summary> /// <param name="id"></param> /// <returns></returns> public static Artist FindById(int id) { return (Artist)FindByPrimaryKey(typeof(Artist), id); } /// <summary> /// Albums /// </summary> [HasAndBelongsToMany(typeof(Album), Table = "ArtistAlbum", ColumnKey = "artistid", ColumnRef = "albumid")] public IList Albums { get { return albums; } set { albums = value; } } /// <summary> /// Get an HTML view of album names for this artis /// </summary> public string HtmlAlbumsNames { get { string AlbumsNames = ""; foreach (Album album in Albums) { AlbumsNames += @"<a href=""/album/edit.rails?id=" + album.Id + @""" />" + album.Name + " (" + album.Year + ")<br/>"; } return AlbumsNames; } } } }
4. Genre Class
using System; using System.Collections; using Castle.ActiveRecord; using NHibernate.Expression; namespace net.paolocorti.Samples.Castle.Tutorial.Models { /// <summary> /// Genre /// </summary> [ActiveRecord] public class Genre : ActiveRecordValidationBase { private Int16 id; private string name; private IList albums = new ArrayList(); /// <summary> /// Primary Key /// </summary> [PrimaryKey(PrimaryKeyType.Native)] public Int16 Id { get { return id; } set { id = value; } } /// <summary> /// Name /// </summary> [Property(NotNull = true, Length = 100), ValidateIsUnique("There is already this genre!"), ValidateNotEmpty("You need to provide the name of the genre!")] public string Name { get { return name; } set { name = value; } } /// <summary> /// Albums /// </summary> [HasAndBelongsToMany(typeof(Album), Table = "AlbumGenre", ColumnKey = "genreid", ColumnRef = "albumid")] public IList Albums { get { return albums; } set { albums = value; } } /// <summary> /// Find all genres /// </summary> /// <returns></returns> public static Genre[] FindAll() { return (Genre[])FindAll(typeof(Genre)); } /// <summary> /// Find genre by id /// </summary> /// <param name="id"></param> /// <returns></returns> public static Genre FindById(Int16 id) { return (Genre)FindByPrimaryKey(typeof(Genre), id); } /// <summary> /// Find genre by name /// </summary> /// <param name="name"></param> /// <returns></returns> public static Genre FindByName(string name) { return (Genre)FindOne(typeof(Genre), Expression.Like("Name", name)); } } }
5. Support Class
using System; using System.Collections; using Castle.ActiveRecord; using NHibernate.Expression; namespace net.paolocorti.Samples.Castle.Tutorial.Models { /// <summary> /// Support /// </summary> [ActiveRecord] public class Support : ActiveRecordValidationBase { private Int16 id; private string name; /// <summary> /// Primary key /// </summary> [PrimaryKey(PrimaryKeyType.Native)] public Int16 Id { get { return id; } set { id = value; } } /// <summary> /// Name /// </summary> [Property(NotNull = true, Length = 50)] public string Name { get { return name; } set { name = value; } } /// <summary> /// Find all supports /// </summary> /// <returns></returns> public static Support[] FindAll() { return (Support[])FindAll(typeof(Support)); } /// <summary> /// Find support by id /// </summary> /// <param name="id"></param> /// <returns></returns> public static Support FindById(Int16 id) { return (Support)FindByPrimaryKey(typeof(Support), id); } /// <summary> /// Find support by name /// </summary> /// <param name="name"></param> /// <returns></returns> public static Support FindByName(string name) { return (Support)FindOne(typeof(Support), Expression.Like("Name", name)); } } }
Now that we wrote our model classes we could autogenerate our db schema (under the RDBMS you have chosen in your configuration web.config file, in the activerecord section.
The best place to do this is in the Application_on_Start event in the GlobalApplication.cs file. There you could even preload some data in your RDBMS, to test if your domain model classes are working fine:
/// <summary> /// Application Start: initialize ActiveRecord and eventually create the schema and add some data /// </summary> public void Application_OnStart() { ActiveRecordStarter.Initialize(ActiveRecordSectionHandler.Instance, new Type[] { typeof(Album), typeof(Genre), typeof(AlbumGenre), typeof(Artist), typeof(Support) }); // comment this out if you want to generate the schema from ActiveRecord and want to add some test data // If you want to let ActiveRecord create the schema for you ActiveRecordStarter.CreateSchema(); // let's create some entity // genres string[] genreList = { "Rock", "Pop", "Blues", "Jazz", "Classic" }; for(int i=0; i<genreList.Length; i++) { Genre newGenre = new Genre(); newGenre.Name = genreList[i]; newGenre.Create(); } Genre rockGenre = Genre.FindByName("Rock"); Genre popGenre = Genre.FindByName("Pop"); // supports string[] supportList = { "CD", "DVD", "Tape", "33", "45" }; for (int i = 0; i < supportList.Length; i++) { Support newSupport = new Support(); newSupport.Name = supportList[i]; newSupport.Create(); } Support cdSupport = Support.FindByName("CD"); // artist Artist newArtist = new Artist(); newArtist.Name = "Nirvana"; newArtist.Create(); // album Album newAlbum = new Album(); newAlbum.Artists.Add(newArtist); newAlbum.Name = "Never Mind"; newAlbum.Year = 1992; newAlbum.Genres.Add(rockGenre); newAlbum.Genres.Add(popGenre); newAlbum.Support = cdSupport; newAlbum.Create(); // albumgenre AlbumGenre[] albumGenre = AlbumGenre.FindByAlbum(newAlbum); for (int i = 0; i < albumGenre.Length; i++) { if (albumGenre[i].Genre.Name == "Pop") { albumGenre[i].Main = true; albumGenre[i].Save(); } } }
Now start your solution: you will be redirected to the index page, where there is nothing, but the Application_OnStart method should have been hit, and in your RBMS you should now see your schema (tables, constraints, relationship, …) added in the correct way. You should also find some data in your RDBMS.
If everything worked fine you can now safely comment out the code in the Application_OnStart method (you don’t need to regenerate every time the schema and add again the sample data!). But you MUST leave the ActiveRecordStarter.Initialize call, because our MonoRail application needs to know which classes should be implemented with ActiveRecord.
So, if you will need to add some more model classes to this sample, just to make some try, do not forget to add these classes in the ActiveRecordStarter.Initialize method, like we did for our 5 model classes:
ActiveRecordStarter.Initialize(ActiveRecordSectionHandler.Instance, new Type[] { typeof(Album), typeof(Genre), typeof(AlbumGenre), typeof(Artist), typeof(Support) });
Next post we will go on with coding and we will start to look at Controllers and Views.










Hi
This is really good work. Do you intend to complete the series by showing us how to hook up the controller and view to the model? I want to see how basic CRUD works in Castle.
Many thanks
Zahir
I am sorry Zahir, I would like to write down the last posts for this tutorial, but currently i am very busy on other projects not related to .NET (and to Castle).
Thanks for your interest
We’ll be waiting :-p
Muito bom trabalho. Estou iniciando com este framwork. Gostei bastante. Eu estou no aguardo do restante do material.