So you've decided to try ActiveRecord but don't know where to start. We can definitely help you on this matter. Just follow the next sections.
A crash course
This tutorial assumes that you have created a simple Console Application to try ActiveRecord (not a windows app, not a web app yet, let's stick with the simplest for now).
We also assume that you know, or at least got a gist of what ActiveRecord is for, so we won't waste your time with fluf.
Necessary assemblies
You must reference the following set of assemblies to use ActiveRecord:
- Castle.ActiveRecord.dll
- Castle.Model.dll
- Nullables.dll
But ActiveRecord also depends on [NHibernate], so you must reference the following as well:
- NHibernate.dll
- Castle.DynamicProxy.dll (Curious? Check [DynamicProxy])
- Nullables.NHibernate.dll
- log4net.dll
- Iesi.Collections.dll
A simple database structure
Now suppose you have the following table structure:
CREATE TABLE Blogs (
blog_id int IDENTITY(1, 1) PRIMARY KEY,
blog_name varchar(50),
blog_author varchar(50)
)
CREATE TABLE Posts (
post_id int IDENTITY(1, 1) PRIMARY KEY,
post_title varchar(50),
post_contents text,
post_category varchar(50),
post_blogid int FOREIGN KEY REFERENCES Blogs (blog_id),
post_created datetime,
post_published bit
)
Creating the classes
Now the fun part begins. Just create a new class named Blog that extends ActiveRecordBase:
(You should have imported Castle.ActiveRecord)
public class Blog : ActiveRecordBase
{
}
However this is not enough for ActiveRecord to understand what table this class is mapped to, so if you also need to decorate the class with the ActiveRecordAttribute:
[ActiveRecord("Blogs")] public class Blog : ActiveRecordBase { }
|
Note that if the table name was 'Blog', then you wouldn't have to specify the name. |
The next step is to specify the primary key (and you must provide a primary key). In this case our primary key is auto generated by the database (column blog_id):
[ActiveRecord("Blogs")] public class Blog : ActiveRecordBase { private int _id; [PrimaryKey(PrimaryKeyType.Native, "blog_id")] public int Id { get { return _id; } set { _id = value; } } }
Also, this could be made simpler if you the column was simply 'id':
[PrimaryKey] public int Id { get { return _id; } set { _id = value; } }
Finally, map the properties. This couldn't be simpler:
[ActiveRecord("Blogs")] public class Blog : ActiveRecordBase { private int _id; private String _name; private String _author; [PrimaryKey(PrimaryKeyType.Native, "blog_id")] public int Id { get { return _id; } set { _id = value; } } [Property("blog_name")] public String Name { get { return _name; } set { _name = value; } } [Property("blog_author")] public String Author { get { return _author; } set { _author = value; } } }
That's it. You're now able to create Blog instances (Create, Update and Delete methods are public and inherited from ActiveRecordBase). But you can add more methods in order to query the database, or delete the rows:
public static void DeleteAll() { DeleteAll( typeof(Blog) ); } public static Blog[] FindAll() { return (Blog[]) FindAll( typeof(Blog) ); } public static Blog Find(int id) { return (Blog) FindByPrimaryKey( typeof(Blog), id ); }
At the end you'll end up with something like the following:
[ActiveRecord("Blogs")] public class Blog : ActiveRecordBase { private int _id; private String _name; private String _author; [PrimaryKey(PrimaryKeyType.Native, "blog_id")] public int Id { get { return _id; } set { _id = value; } } [Property("blog_name")] public String Name { get { return _name; } set { _name = value; } } [Property("blog_author")] public String Author { get { return _author; } set { _author = value; } } public static void DeleteAll() { DeleteAll( typeof(Blog) ); } public static Blog[] FindAll() { return (Blog[]) FindAll( typeof(Blog) ); } public static Blog Find(int id) { return (Blog) FindByPrimaryKey( typeof(Blog), id ); } }
As you see the code is very straightforward. We mapped the class to a table, the fields, and the primary key. Now, before you go and run some test code, we must start the framework properly. In order to do that you must provide some configuration information.
Read more Configuration Reference
Starting the framework
Before you use ActiveRecord, you need to provide the information about which database are you using. You can keep this information in the AppDomain configuration file, or just hardcode it in the application - for the sake of clarity:
// The following requires: using Castle.ActiveRecord.Framework.Config; InPlaceConfigurationSource source = new InPlaceConfigurationSource(); Hashtable properties = new Hashtable(); properties.Add("hibernate.connection.driver_class", "NHibernate.Driver.SqlClientDriver"); properties.Add("hibernate.dialect", "NHibernate.Dialect.MsSql2000Dialect"); properties.Add("hibernate.connection.provider", "NHibernate.Connection.DriverConnectionProvider"); properties.Add("hibernate.connection.connection_string", "UID=sa;Password=mypass;Initial Catalog=test;Data Source=."); source.Add( typeof(ActiveRecordBase), properties ); ActiveRecordStarter.Initialize( source, typeof(Blog) );
If you want to use an external configuration file, it will look like the following:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="activerecord" type="Castle.ActiveRecord.Framework.Config.ActiveRecordSectionHandler, Castle.ActiveRecord"/> </configSections> <activerecord> <config> <add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" /> <add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect" /> <add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" /> <add key="hibernate.connection.connection_string" value="UID=sa;Password=yourpass;Initial Catalog=test;Data Source=." /> </config> </activerecord> </configuration>
In this case, you can just initialize like this:
IConfigurationSource source = System.Configuration.ConfigurationSettings.GetConfig("activerecord") as IConfigurationSource; ActiveRecordStarter.Initialize( source, typeof(Blog) );
IConfigurationSource source = System.Configuration.ConfigurationManager.GetSection("activerecord") as IConfigurationSource; ActiveRecordStarter.Initialize( source, typeof(Blog) );
Important: you need add Post class in ActiveRecordStarter.Initialize<br>
You can then perform your operations with the objects:
Blog.DeleteAll(); Blog blog = new Blog(); .. set fields .. blog.Save(); // or blog.Create(); ... more operations ... blog.Save(); // or blog.Update(); ... tired of this blog? blog.Delete();
For more on mappings see Mappings
Adding relations mapping
One of the beauties of having a DomainModel implemented using ActiveRecord is how it makes simple to create interelations within your object model. In this case we have created a Blog class, and it's fair enough to provide a Post class and the relation between them. In plain english the relation is
- A Post belongs to a Blog
- A Blog has many Posts
Using HasManyAttribute and BelongsToAttribute
To express these relations you might use these attributes. For example:
[ActiveRecord("Blogs")] public class Blog : ActiveRecordBase { ... private IList _posts; ... [HasMany(typeof(Post), Table="posts", ColumnKey="post_blogid")] public IList Posts { get { return _posts; } set { _posts = value; } } } [ActiveRecord("Posts")] public class Post : ActiveRecordBase { ... private Blog _blog; ... [BelongsTo("post_blogid")] public Blog Blog { get { return _blog; } set { _blog = value; } } }
In this case, as the Post class has a BelongsTo association, ActiveRecord can collect the information to create the correct HasMany, so you really don't need to specify all information:
[ActiveRecord("Blogs")] public class Blog : ActiveRecordBase { ... private IList _posts; ... [HasMany(typeof(Post))] public IList Posts { get { return _posts; } set { _posts = value; } } }
If you're wondering, this is how the Post class should look like:
[ActiveRecord("Posts")] public class Post : ActiveRecordBase { private int _id; private String _title; private String _contents; private String _category; private DateTime _created; private bool _published; private Blog _blog; public Post() { _created = DateTime.Now; } public Post(String title) : this() { _title = title; } public Post(Blog blog, String title, String contents, String category) : this() { _blog = blog; _title = title; _contents = contents; _category = category; } [PrimaryKey(PrimaryKeyType.Native, "post_id")] public int Id { get { return _id; } set { _id = value; } } [Property("post_title")] public String Title { get { return _title; } set { _title = value; } } [Property("post_contents",ColumnType="StringClob")] public String Contents { get { return _contents; } set { _contents = value; } } [Property("post_category")] public String Category { get { return _category; } set { _category = value; } } [BelongsTo("post_blogid")] public Blog Blog { get { return _blog; } set { _blog = value; } } [Property("post_created")] public DateTime Created { get { return _created; } set { _created = value; } } [Property("post_published")] public bool Published { get { return _published; } set { _published = value; } } public static void DeleteAll() { ActiveRecordBase.DeleteAll( typeof(Post) ); } public static Post[] FindAll() { return (Post[]) ActiveRecordBase.FindAll( typeof(Post) ); } }
Using the relation
A big source of confusion is how to use the relation (at least with the cascading defaults), for example, the following code '''is not going to work'''
Blog blog = Blog.Find(1); blog.Posts.Add( new Post("This is my first post") ); blog.Save(); // Exception!
That's because the Post instance in this case would be a transient class, not persisted. It must be persisted first. So this is the correct code:
Blog blog = Blog.Find(1); Post post = new Post("This is my first post"); post.Save(); // Saving it first blog.Posts.Add( post ); blog.Save(); // Now it's OK
You can also associate the Post with the blog using the other side of the relation:
Blog blog = Blog.Find(1); Post post = new Post("This is my first post"); post.Blog = blog; // Linking them post.Save();
But of course, don't assume that when you do that the blog.Posts will automatically be notified and will then have one element, you must "refresh" it if you want it to reflect the latest changes.
You can also change the relation cascading settings (on the Blog class):
[HasMany(typeof(Post), Table="Posts", ColumnKey="post_blogid", Cascade=ManyRelationCascadeEnum.SaveUpdate)] public IList Posts { get { return _posts; } set { _posts = value; } }
On this case the following code is going to work:
Blog blog = Blog.Find(1); blog.Posts.Add( new Post("This is my first post") ); blog.Save(); // Ok
Finding records
We could but we rather not expose public methods to find records on ActiveRecordBase, and if you're wondering why, we tell you: static types. We rather have the compiler on our side on this matter, so we kinda obligate you to provide the find methods, which wont be nothing more than delegating to ActiveRecordBase protected methods.
FindByPrimaryKey
A Find method will usually be implemented like this:
public static Blog Find(int id) { return (Blog) FindByPrimaryKey(typeof(Blog), id); }
In this case, if the record is not found an exception will be the thrown. If you dont want that, and expected only a null return, then change to that:
public static Blog Find(int id) { return (Blog) FindByPrimaryKey(typeof(Blog), id, false); }
FindAll
The FindAll allows you to defined an order by and a criteria, which can go from simple to complex: For most cases, instead of using FindAll you can go with FindAllByProperty:
public static Blog[] FindByName(String name) { return (Blog[]) FindAllByProperty(typeof(Blog), "Name", name); }
Please note that we're using a property name, not a column name.
If you just want all records:
public static Blog[] FindAll() { return (Blog[]) FindAll(typeof(Blog)); }
With a criteria (You should have imported NHibernate.Expression):
public static Blog[] FindByAuthor(String author) { return (Blog[]) FindAll(typeof(Blog), Expression.Eq("Author", author)); }
With an order and criteria:
public static Blog[] FindByAuthor(String author) { return (Blog[]) FindAll(typeof(Blog), Expression.Eq("Author", author)); }
FindFirst and FindOne
Sometimes you want the first record found the a criteria, and sometimes you're expecting only one record or none to exists on the database. In those cases use FindFirst or FindOne:
public static Blog FindFirstBlogByAuthor(String author) { return (Blog) FindFirst(typeof(Blog), Expression.Eq("Author", author)); }
Batching changes
If you know NHibernate, you know that every set of operation must be enclosed within a valid session. ActiveRecord encapsulates and even hides operations with the session, but even so you can gain access to it (see Using HQL) and more importantly, batch operations to be performed together. Using an earlier example:
using(new SessionScope()) { Blog blog = Blog.Find(1); Post post = new Post("This is my first post"); post.Save(); blog.Posts.Add( post ); blog.Save(); } // The changes will be sent to the DB when the session is disposed here
Final thoughts
This was an introductory tutorial. There are plenty to be said about ActiveRecord and we encourage you to try, bend and push it. In a few hours you'll probably have mastered it.
