Welcome Guest, you are in: Login

Castle Project

RSS RSS

Navigation (Active Record)





Search the wiki
»

PoweredBy

Mappings

RSS
Modified on 2011/01/03 02:12 by Jan Wilson Categorized as Uncategorized

Table of Contents [Hide/Show]


Mappings
   Simple mappings
      Class
      Primary key
      Composite Keys
      Properties
      Fields
      Nested
      Version/Timestamp
   BelongsTo
   HasMany
   HasAndBelongsToMany
   OneToOne
      Glitches
   Any
   HasManyToAny


First of all, you must have in mind that ActiveRecord uses NHibernate, so it's a good idea to at least have a look at how NHibernate works. Their documentation is very good, and the mappings section is something that you must read.

Mappings

In this section we're going to talk more about how to perform proper mappings. However, we won't talk about every single property and enum value. Please check the API section for further detailed information.

Simple mappings



Class

Every ActiveRecord class must extend ActiveRecordBase and must use the ActiveRecordAttribute.

[ActiveRecord("blogtable")]
public class Blog : ActiveRecordBase<Blog>
{
}

You can omit the table name if the class name has the same name as the table.

[ActiveRecord]
public class Blog : ActiveRecordBase<Blog>
{
}

Primary key



[ActiveRecord]
public class Blog : ActiveRecordBase<Blog>
{
  [PrimaryKey]
  public int Id {get; set; }
}

Which is equivalent to use:

[PrimaryKey(PrimaryKeyType.Native)]
public int Id {get; set; }

You can use sequences as well:

[PrimaryKey(PrimaryKeyType.Sequence, SequenceName="myseqname")]
public int Id {get; set; }

Composite Keys

To use composite keys, all the work is in the composite key itself. Just use a PrimaryKey attribute as you normally would, with the type of the property being your composite key class.

[PrimaryKey]
public MyCompositeKey ID {get; set; }

... and the composite key class ...

[CompositeKey, Serializable]
public class MyCompositeKey
{
  [KeyProperty]
  public virtual string KeyA {get; set; }

  [KeyProperty]
  public virtual string KeyB {get; set;}

  public override string ToString()
  {
    return string.Join( ":", new string[] { KeyA, KeyB } );
  }

  public override bool Equals( object obj )
  {
    if( obj == this ) return true;
    if( obj == null || obj.GetType() != this.GetType() ) return false;
    MyCompositeKey test = ( MyCompositeKey ) obj;
    return ( KeyA == test.KeyA || (KeyA != null && KeyA.Equals( test.KeyA ) ) ) &&
      ( KeyB == test.KeyB || ( KeyB != null && KeyB.Equals( test.KeyB ) ) );
  }

  public override int GetHashCode()
  {
    return KeyA.GetHashCode() ^ KeyB.GetHashCode();
  }
}

A composite key class must be Serializable, and also implement *both* Equals and GetHashCode. The class must also have two or more properties marked with the KeyProperty attribute.

Properties

To map an ordinary property where its name is the same as the column, use:

[Property]
public String Name {get; set; }

Or you can specify the column name:

[Property("customer_name")]
public String Name {get; set; }

You can also set more detailed information:

[Property(Length=10, NotNull=true, Unique=true)]
public String Name {get; set; }

Fields

You can also map a field directly, using the Field Attribute:

[Field]
private String _name;

Like with a property, you can specify the column name:

[Field("customer_name")]
private String _name;

You can specify detailed information on a Field the same way you do it on a property. The field can be of any visibility.

Using is a field is usually recommended when the field data is an internal class implementation (type of a strategy to use in a certain case, which is never exposed to the clients of the class, etc). In general, it's preferable to use properties.

Nested

You can use an aggregate class to map data on the same table. For example:

[ActiveRecord]
public class Company : ActiveRecordBase<Company>
{
  [Nested]
  public PostalAddress Address {get; set; }
}

public class PostalAddress
{
  [Property]
  public String Address {get; set; }

  [Property]
  public String City {get; set; }

  [Property]
  public String State {get; set; }

  [Property]
  public String ZipCode {get; set; }
}

Version/Timestamp

The NHibernate's Version and Timestamp feature can be used on ActiveRecord as well:

[Version("customer_version")]
public Int32 Version {get; set; }

And

[Timestamp("customer_timestamp")]
public Int32 Timestamp {get; set; }

BelongsTo

The BelongsTo maps a many to one association, like Post belongs to Blog:

[ActiveRecord]
public class Post : ActiveRecordBase<Post>
{
    [BelongsTo("post_blogid")]
    public Blog Blog {get; set; }
}

You can also specify the Cascade behavior, enable insert and update and so forth:

[BelongsTo("post_blog_id", Cascade=CascadeEnum.All, Unique=true)]
public Blog Blog {get; set; }

HasMany

The HasMany maps an one to many association, like Blog has many Post.

[ActiveRecord("Blogs")]
public class Blog : ActiveRecordBase<Blog>
{
    private List<Post> _posts;

    [HasMany(typeof(Post), Table="posts", ColumnKey="post_blogid")]
    public IList<Post> Posts
    {
        get { return _posts; }
        set { _posts = value; }
    }
}

You can be explicit on the mapping information or you can omit it if the target class has a BelongsTo mapping back to the source class.

You can also specify the cascade behavior, order by, where, turn on the lazy. Also, you can use IList or ISet to hold the collection items. If you need to delete child objects when deleting a parent (e.g. when calling Blog.Delete() you should also delete all related Post records from the database), you can specify Inverse=true. See NHibernate's Parent/Child documentation for more examples.

HasAndBelongsToMany

The HasAndBelongsToMany maps a many to many association using an separated association table.

Consider the following tables:

CREATE TABLE [dbo].[companies] (
    [id] [int] IDENTITY (1, 1) NOT NULL ,
    [client_of] [int] NULL ,
    [name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [type] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[people] (
    [id] [int] IDENTITY (1, 1) NOT NULL ,
    [name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
) ON [PRIMARY]

CREATE TABLE [dbo].[people_companies] (
    [person_id] [int] NOT NULL ,
    [company_id] [int] NOT NULL
) ON [PRIMARY]

As you see a company has many people and a "person" has many companies. This kind of mapping can be achieved using the attribute HasAndBelongsToMany:

[ActiveRecord("companies")]
public class Company : ActiveRecordBase<Company>
{
    private List<Person> _people = new List<Person>;

    [PrimaryKey]
    public int Id {get; set; }

    [Property]
    public String Name {get; set; }

    [HasAndBelongsToMany( typeof(Person),
      Table="people_companies",
      ColumnRef="person_id", ColumnKey="company_id" )]
    public IList<Person> People
    {
        get { return _people; }
        set { _people = value; }
    }
}

[ActiveRecord("people")]
public class Person : ActiveRecordBase<Person>
{
    private List<Company> _companies = new List<Company>();

    [PrimaryKey]
    public int Id
    {
        get { return _id; }
        set { _id = value; }
    }

    [Property]
    public string Name {get; set; }

    [HasAndBelongsToMany( typeof(Company),
      Table="people_companies",
      ColumnRef="company_id", ColumnKey="person_id" )]
    public IList<Company> Companies
    {
        get { return _companies; }
        set { _companies = value; }
    }
}

You must specify the association table and the ref and key columns.

OneToOne

A One to one associates a foreign table where the current class and the target class share their primary key.

This is usefull when you are using Class Table Inheritance

Glitches

The primary key of the table which isn't autogenerated must be declared using PrimaryKeyType.Foreign:

[ActiveRecord("Customer")]
public class Customer : ActiveRecordBase<Customer> 
{
	[PrimaryKey]
	public int CustomerID {get; set; }

	[OneToOne]
	public CustomerAddress CustomerAddress { get; set; }

	[Property]
	public string Name {get; set; }
}

[ActiveRecord("CustomerAddress")]
public class CustomerAddress : ActiveRecordBase<CustomerAddress> 
{
	[PrimaryKey(PrimaryKeyType.Foreign)]
	public int CustomerID {get; set; }

	[OneToOne]
	public Customer Customer {get; set; }

	[Property]
	public string Address {get; set; }
}

Any

There are certain cases when you need to make an association from an entity to a range of possible objects that doesn't necessarily share a common base class.

This is a fairly advanced scenario, if you can, find a simpler solution.

A simple example may be a payment method in an Order class, where the choices are either a bank account or a credit card, like this:

[ActiveRecord("CreditCards")]
public class CreditCard : ActiveRecordBase<CreditCard>, IPaymentMethod
{ ... }

[ActiveRecord("BankAccounts")]
public class BankAccount : ActiveRecordBase<BankAccount>, IPaymentMethod
{ ... }

As you can see, when I try to write my Order class, I do not know how to map those two options. They are not part of any hierarchy (either in the object model or an ActiveRecord one). The solution is to map them to this schema:

CREATE TABLE Orders
(
  Id int not null identity(1,1),
  ...
  Billing_Details_Id int not null,
  Billing_Details_Type nvarchar not null,
)

Together, BillingDetailsId and BillingDetailsType points to the correct account or credit card that should pay for the order. Here is the attributes declarations.
Unlike most other attributes, here you need to specify a few attributes.

[Any(typeof(int), MetaType=typeof(string),
	TypeColumn="Billing_Details_Type",
	IdColumn="Billing_Details_Id",
	Cascade=CascadeEnum.SaveUpdate)]
[Any.MetaValue("CREDIT_CARD", typeof(CreditCard))]
[Any.MetaValue("BANK_ACCOUNT", typeof(BankAccount))]
public IPaymentMethod PaymentMethod {get; set; }

The first parameter is the type of the Id column (in this case "BillingDetailsId"), the second is the MetaType definition, which in this case mean the type of the the field that defines the type of the id. Next we have the TypeColumn and IdColumn, which match Billing_Details_Type and Billing_Details_Id. The interesting part is the Any.MetaValue() attribute. Here, we define that when the value in the Billing_Details_Type column is "CREDIT_CARD", the value in the Billing_Details_Id column is the primary key of a CreditCard, and when the Billing_Details_Type is BANK_ACCOUNT, then the value in Billing_Details_Id should be interpreted as the primary key of a BankAccount class.
The type of the property should be of a common type or interface that all the possible objects share (worst case is to make it of type System.Object).

HasManyToAny

A natural extention of Any, the HasManyToAny provides the same functionality for collections. Here is an example of a class that needs a set of payment methods:

[HasManyToAny(typeof (IPaymentMethod), "pay_id", "payments_table", typeof (int), "Billing_Details_Type", "Billing_Details_Id",
	MetaType=typeof (string))]
[Any.MetaValue("CREDIT_CARD", typeof (CreditCard))]
[Any.MetaValue("BANK_ACCOUNT", typeof (BankAccount))]
public ISet<IPaymentMethod> PaymentMethod { get { ... } set { ... } }

The parameters for HasManyToAny are (in order of apperances in the constructor):
  • typeof(IPaymentMethod) - the type of the objects in this collection
  • pay_id - the key column that maps the values in this collection to this object
  • payment_table - the table for this collection
  • typeof(int) - the type of the id column - identical to the first parameter of \Any\
  • Billing_Details_Type - identical in function to the Billing_Details_Type mentioned above
  • Billing_Details_Id - identical to the Billing_Details_Id mentioned above
  • MetaType=typeof(string - the type of the type column - identical to the one above

ScrewTurn Wiki version 3.0.4.560. Some of the icons created by FamFamFam.