ActiveRecord:映射
来自 Castle,Stephen WJJ @GZ 译于 2006年3月29日。
首先,你一定注意到了 ActiveRecord 使用 NHibernate,因此,至少阅读一下 NHibernate 是如何工作的会是个不错的主意。他们的文档 写得很好,其中 映射部分 是你一定要看的。
内容目录 |
映射
这一部分我们准备更多地谈论如何进行恰当的映射。然而我们不会谈论每一个独立的属性和枚举值。更详细的细节请阅读 API 部分。
简单的映射
类
每个 ActiveRecord 类一定要继承自 ActiveRecordBase 并且一定要使用 ActiveRecordAttribute 标注。
[ActiveRecord("blogtable")] public class Blog : ActiveRecordBase { }
如果类名称和数据表名称是相同的,那么你可以省略数据表名称。
[ActiveRecord()] public class Blog : ActiveRecordBase { }
主键
[ActiveRecord()] public class Blog : ActiveRecordBase { private int id; [PrimaryKey] public int Id { get { return id; } set { id = value; } } }
这等价于:
[PrimaryKey(PrimaryKeyType.Native)] public int Id { get { return id; } set { id = value; } }
你也可以使用 sequences:
[PrimaryKey(PrimaryKeyType.Sequence, SequenceName="myseqname")] public int Id { get { return id; } set { id = value; } }
复合主键
要使用复合主键,所有要做的工作就是复合主键本身。定义一个复合主键类,然后使用一个你通常也会用到的、带有 PrimaryKey 标注的属性,并且设定它的返回类型为之前定义的那个复合主键类。
[PrimaryKey] public MyCompositeKey ID { get { return _key; } set { _key = value; } }
... 以下是复合主键类的定义 ...
[CompositeKey, Serializable] public class MyCompositeKey { private string _keyA; private string _keyB; [KeyProperty] public virtual string KeyA { get { return _keyA; } set { _keyA = value; } } [KeyProperty] public virtual string KeyB { get { return _keyB; } set { _keyB = value; } } 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(); } }
复合主键类必须是可序列化的,也要同时实现 Equals 和 GetHashCode 方法。这个类也必须具有两个或以上的属性被标记上 KeyProperty 标注。
属性
映射一个名字和字段名相同的原始属性, 使用:
[Property] public String Name { get { return name; } set { name = value; } }
或者你也可以指定字段名:
[Property("customer_name")] public String Name { get { return name; } set { name = value; } }
你也可以设定更多细节信息,例如长度、非空、唯一性等:
[Property(Length=10, NotNull=true, Unique=true)] public String Name { get { return name; } set { name = value; } }
字段
你也可以使用 [Field] 标注直接映射一个字段:
[Field] private String _name;
正如属性那样,你也可以指定字段名:
[Field("customer_name")] private String _name;
你可以像属性那样在一个字段上指定细节信息。这个字段可以具有任何可见性(public / protected / private 等)。
通常当字段数据是一个内部类实现(这是一种在某种情况下使用的策略,其目的是避免把内部类的实现暴露给使用宿主类的环境)的时候建议使用字段。通常情况下,建议使用属性。
嵌套
你可以使用一个聚合类把数据映射到相同的数据表上。那么,嵌套类中被标注为 [Property] 的属性其实就代表了宿主类相应的数据表的字段。例如:
[ActiveRecord] public class Company : ActiveRecordBase { private PostalAddress _address; [Nested] public PostalAddress Address { get { return _address; } set { _address = value; } } } public class PostalAddress { private String _address; private String _city; private String _state; private String _zipcode; public PostalAddress() { } public PostalAddress(String address, String city, String state, String zipcode) { _address = address; _city = city; _state = state; _zipcode = zipcode; } [Property] public String Address { get { return _address; } set { _address = value; } } [Property] public String City { get { return _city; } set { _city = value;} } [Property] public String State { get { return _state; } set { _state = value; } } [Property] public String ZipCode { get { return _zipcode; } set { _zipcode = value; } } }
版本和时间戳
NHibernate 的版本和时间戳特性也可以用在 ActiveRecord 里:
[Version("customer_version")] public Int32 Version { get { return version; } set { version = value; } }
And
[Timestamp("customer_timestamp")] public Int32 Timestamp { get { return ts; } set { ts = value; } }
BelongsTo 解决多对一关系
BelongsTo 映射一个多对一关系,正如 Post(s) 属于 Blog 一样:
[ActiveRecord] public class Post : ActiveRecordBase { private Blog blog; [BelongsTo("post_blogid")] public Blog Blog { get { return blog; } set { blog = value; } } }
你也可以指定 Cascade (级联)行为,启用级联插入、级联更新等等:
[BelongsTo("post_blog_id", Cascade=CascadeEnum.All, Unique=true)] public Blog Blog { get { return blog; } set { blog = value; } }
HasMany 解决一对多关系
HasMany 映射一对多关系,就像 Blog 拥有多个 Post 一样:
[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; } } }
请注意,你既可以强制设定映射信息,或者当目标类具有一个 BelongsTo 映射回到这个源类的话,你也可以忽略那些映射信息。
你还可以指定 cascade (级联)行为、指定order by 排序、指定 where 条件以及开启延迟载入。你也可以使用 IList 或者 ISet 储存集合项目。如果你当你在删除父对象时需要删除子对象(级联删除,例如当调用 Blog.Delete() 方法时也应该从数据库删除所有相关的 Post 文章记录),你可以指定 Inverse=true。更多示例请参阅 NHibernate 的父子关系文档。
HasAndBelongsToMany 解决多对多关系
HasAndBelongsToMany 通过使用一个独立的关系数据表来映射一个多对多关系。
考虑如下的数据表:
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]
如你所见,一间公司拥有很多人,同时某个人也与多间公司有关系。这种映射可以使用 HasAndBelongsToMany 标注进行编写。
[ActiveRecord("companies")] public class Company : ActiveRecordBase { private int id; private String name; private IList _people; public Company() { } public Company(string name) { this.name = name; } [PrimaryKey] public int Id { get { return id; } set { id = value; } } [Property] public String Name { get { return name; } set { name = value; } } [HasAndBelongsToMany( typeof(Person), Table="people_companies", ColumnRef="person_id", ColumnKey="company_id" )] public IList People { get { return _people; } set { _people = value; } } } [ActiveRecord("people")] public class Person : ActiveRecordBase { private int _id; private String _name; private IList _companies; public Person() { _companies = new ArrayList(); } [PrimaryKey] public int Id { get { return _id; } set { _id = value; } } [Property] public string Name { get { return _name; } set { _name = value; } } [HasAndBelongsToMany( typeof(Company), Table="people_companies", ColumnRef="company_id", ColumnKey="person_id" )] public IList Companies { get { return _companies; } set { _companies = value; } } }
请注意,你一定要指定关联的数据表以及 ColumnRef(对应被引用的主键)和 ColumnKey(自己的主键)。
OneToOne 解决一对一关系
一对一关系就是当前类和目标类之间共享它们的主键的一个外部数据表。
当你使用 类-表 继承 时这很有用。
外部主键
当数据表的主键不是自动产生时必须使用 PrimaryKeyType.Foreign 定义:
[ActiveRecord("Customer")] public class Customer : ActiveRecordBase { int _custID; string name; CustomerAddress _addr; [PrimaryKey] public int CustomerID { get { return _custID; } set { _custID = value; } } [OneToOne] public CustomerAddress CustomerAddress { get { return _addr; } set { _addr = value; } } [Property] public string Name { get { return _name; } set { _name = value; } } } [ActiveRecord("CustomerAddress")] public class CustomerAddress : ActiveRecordBase { int _custID; string address; Customer _cust; [PrimaryKey(PrimaryKeyType.Foreign)] public int CustomerID { get { return _custID; } set { _custID = value; } } [OneToOne] public Customer Customer { get { return _cust; } set { _cust = value; } } [Property] public string Address { get { return _addr; } set { _addr = value; } } }