Table of contents
- 1 ActiveRecordBase
- 1.1 ActiveRecordValidationBase
- 1.1 ActiveRecordValidationBase
- 2 Typed collections
Generics support
If you happen to be using .Net Framework 2.0, you might benefit from the generic version of base classes.
It is also possible to use typed collections instead of the IList or ISet
ActiveRecordBase<T>
When you inherit from ActiveRecordBase
using Castle.ActiveRecord; [ActiveRecord] public class Customer : ActiveRecordBase<Customer> { // Mapping omitted as it is the same }
We can use several operations without the need to implement them.
// Find all Customer[] customers = Customer.FindAll(); // Find by property customers = Customer.FindAllByProperty("Name", "John doe"); // Find by primary key Customer cust = Customer.Find( 1 );
ActiveRecordValidationBase<T>
Additionally, a version of ActiveRecordValidationBase
Typed collections
If you want to use the strongly typed, generic versions of the collections, you'll need a separate extension, called NHibernate.Generics. This extension was developed by our fellow Castle member Ayende Rahien, and it can be found at his web site.
This article is about combining the use of NHibernate.Generics and ActiveRecord to build a domain model that will be natural for programmers in .NET 2.0.
NHibernate.Generics depends on NHibernate 1.0.2, which is not supported by Castle ActiveRecord versions prior to RC1.
Let's take the following sample tables:
CREATE TABLE [dbo].[Blogs] ( [blog_id] [int] IDENTITY (1, 1) NOT NULL , [blog_name] [varchar] (50) NULL , [blog_author] [varchar] (50) NULL ) ON [PRIMARY] CREATE TABLE [dbo].[Posts] ( [post_id] [int] IDENTITY (1, 1) NOT NULL , [post_title] [varchar] (50) NULL , [post_contents] [text] NULL , [post_category] [varchar] (50) NULL , [post_blogid] [int] NULL , [post_created] [datetime] NULL , [post_published] [bit] NULL ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
The domain model that we want is a Blog class that holds a collection of Posts, and a Post class that holds a reference to its parent Blog.
First things first, let's implement the Blog class:
[ActiveRecord] public class Blog : ActiveRecordBase<Blog> { private EntitySet<Post> _posts; private int _id; private string _name; private string _author; public Blog() { _posts = new EntitySet<Post>( delegate(Post p) { p.Blog = this; }, delegate(Post p) { p.Blog = null; } ); } public Blog(string name, string author) : this() { this._name = name; this._author = author; } [PrimaryKey(PrimaryKeyType.Native, "blog_id", Access=PropertyAccess.NoSetterCamelCaseUnderscore)] public int Id { get { return _id; } } [Property("blog_name")] public string Name { get { return _name; } set { _name = value; } } [Property("blog_author")] public string Author { get { return _author; } set { _author = value; } } [HasMany(typeof (Post), "post_blogId", "Posts", Inverse=true, CustomAccess = Generics.Access,RelationType = RelationType.Set)] public ICollection<Post> Posts { get { return _posts; } } }
You must initialize the collection instance when using custom accessors like the example above.
What we have defined so far:
- The standard set of properties (name, author)
- The primary key is created by the database, so there is no reason to expose a setter for it. We are making use of the Access modifier and tells it that it should access the field directly when setting it. We are also giving it the way to find the field (camel case with leading underscore)
- We have an EntitySet<> collection and we initialize it in the constructor.
The EntitySet<> is exposed as an ICollection
, which is the way you expect to work with collections in .Net 2.0
The property name must be same as the field name. Otherwise an exception will happen.
The Posts collections deserves a special attention. The attribute decorating it is a complex one, so let's take it one piece at a time:
- [HasMany(typeof(Post): The property is defined as the many side of a one-to-many relation of Posts
- "blog_id", "Posts"...: The column to search the current object id and the table to search
- Inverse=true: The relationship is maintained on the other end. (We'll see how when we examine the Post class)
- CustomAccess=Generics.Access: This property has a special accessing method, the Generics.Access is merely a helper to avoid cluttering the declaration even more. We need it for ActiveRecord and EntitySet to work together smoothly
- RelationType=RelationType.Set]: We need to explicitly state the type of the relationship
Now that we've taken the attribute apart, all we need to understand is that this lengthy attribute is telling ActiveRecord: this is a generic collection of Posts.
Now let's take a look at the constructor, we are doing something very strange there.
_posts = new EntitySet<Post>{ delegate(Post p) { p.Blog = this; }, delegate(Post p) { p.Blog = null; } );
We are creating a new EntitySet of Posts and telling it how to maintain the relation between a Blog and a Post. We are using the C# 2.0's anonymous delegate feature, which allows us to specify it very cleanly.
- delegate(Post p) { p.Blog = this; }: When a post is added to this collection, set its Blog property to this Blog
- delegate(Post p) { p.Blog = null; }: When a post is removed from this collection, clear its Blog property
And that is all you need to do. The relation is created automatically at this point. These definitions allow you to use the following:
Blog blog = Blog.Load(1); Post post = new Post("First Post", "My First Post", "Slashdot"); blog.Posts.Add(post); post.Save();
Now let's take a look at the Post class:
[ActiveRecord] public class Post : ActiveRecordBase<Post> { private int _id; private string _title; private string _content; private string _category; private DateTime _created; private bool _published; private EntityRef<Blog> _blog; public Post() { _blog = new EntityRef<Blog>( delegate(Tests.Blog b) { b.Posts.Add(this); }, delegate(Tests.Blog b) { b.Posts.Remove(this); } ); } public Post(string title, string content, string category):this() { this._title = title; this._content = content; this._category = category; this._created = DateTime.Now; this._published = false; } [PrimaryKey(PrimaryKeyType.Native,"post_id", Access=PropertyAccess.NoSetterCamelCaseUnderscore)] public int Id { get { return _id; } } [Property("post_title")] public string Title { get { return _title; } set { _title = value; } } [Property("post_content")] public string Content { get { return _content; } set { _content = value; } } [Property("post_category")] public string Category { get { return _category; } set { _category = value; } } [Property("post_created", Access=PropertyAccess.NoSetterCamelCaseUnderscore)] public DateTime Created { get { return _created; } } [Property("post_published")] public bool Published { get { return _published; } set { _published = value; } } [BelongsTo("post_blog_id", CustomAccess=Generics.Access)] public Blog Blog { get { return _blog.Value; } set { _blog.Value = value; } } }
Most of the definitions here are straightforward, so I'll skip them. Now we get to the Blog property, and you can see that we defined an EntityRef<> object to holds it. The reason for that is that you need to maintain the relationship on this side as well, so we again tell ActiveRecord to use custom access strategy.
Then, in the constructor, we are creating the EntityRef<> and instructing it how to create a connection when it is assigned.
_blog = new EntityRef<Blog>( delegate(Tests.Blog b) { b.Posts.Add(this); }, delegate(Tests.Blog b) { b.Posts.Remove(this); } );
- delegate(Tests.Blog b) { b.Posts.Add(this); }: Add this post to the Posts collection of the Blog that was assigned to you
- delegate(Tests.Blog b) { b.Posts.Remove(this); }: Remove this post from the Post collection of the previous Blog that this post was attached to
Another thing that we need to pay attention to is the Blog property.
get { return _blog.Value; }
set { _blog.Value = value; }
We are not using the _blog field directly, rather we are getting and setting on its Value property. This allows the EntityRef to handle the relationships correctly.
That is all you need to do to add generics capabilities to your domain model.