接口式实体定义之——自定义实体属性+实体多根继承

本文介绍NBear的接口式实体定义方式下的自定义实体属性和实体多根继承。本文中的介绍的内容及对应的代码同样包含于最新版本的NBear及其用户手册中。

从SF.Net下载NBear及用户手册

从博客园下载用户手册

最新版本的NBear中除了本文中提到的两个功能之外,还包括如下内容:

1)支持EntityFactory.CreateObject和CreateObjectList现在支持基于DataSet或IDataReader中的字段名称而不仅仅是原来的基于字段顺序的数据填充了;
2)Gateway.Save和Insert方法现在支持自动返回新插入的纪录的自增长ID字段了(当然,前提是,这个实体对应的表确实使用自增长主键字段)。

自定义实体属性

什么是CustomProperty呢?

CustomProperty是一种可以为Entity添加的,不映射到数据表字段的,只读的,用于解析Clob或Blog属性的,自定义类型的属性。

简单的说,如果你的实体包含Clob或Blob大字段,而又想方便的直接读取大字段真正代表的内容,就可以给Entity定义CustomProperty,来封装对大字段内容的访问。

让我们用一个实例来说明:

假设有这样一个Entity:

   public interface EntityWithCustomProperty : IEntity
   {
       [PrimaryKey]
       int ID { get; }
       string Name { get; set; }
       string XmlServerConfig { get; set; }
       string XmlContactConfig { get; set; } 

       [CustomProperty("XmlServerConfig", "XmlContactConfig")]
       SampleCustomPropertyType SampleProperty { get; }
   }

 我们这个Entity除了一些标准的映射到数据表字段的属性(ID,Name,XmlServerConfig和XmlContactConfig)之外,还包含了一个SampleProperty属性。 

首先,这个属性是只读的(只有get,注意,必须设为只读,否则,运行时使用该Entity会报错),它的返回类型是一个自定义类型SampleCustomProperty,它的定义如下: 

   [Serializable]
   public class SampleServerConfig
   {
       public string ServerAddress;
       public int ServerPort;
   }

   [Serializable]
   public class SampleContactConfig
   {
       public string WorkPhone;
       public string HomePhone;
   } 

   public class SampleCustomPropertyType : CustomPropertyType
   {
       public SampleServerConfig ServerConfig
       {
           get
           {
               return SerializeHelper.Deserialize<SampleServerConfig>(typeof(SampleServerConfig), (string)values[0]);
           }
       }
 
       public SampleContactConfig ContactConfig
       {
           get
           {
               return SerializeHelper.Deserialize<SampleContactConfig>(typeof(SampleContactConfig), (string)values[1]);
           }
       }
 
       public SampleCustomPropertyType(object[] inputValues) : base(inputValues)
       {
       }
   }
 
请注意加粗的代码,这个SampleCustomProperty类是一个定义于任意位置的自定义类,对该类的唯一约束是必须从基类NBear.Common.CustomPropertyType继承。因为
基类包含一个protected的构造函数,所以,实现类必须包含同样列表的构造函数,并调用基类构造函数。
 
这个类的内容很简单,它包含两个属性,分别也是两个自定义的config类。values[0]和values[1]分别是这两个config类的XML序列化文本。在这两个属性的get实现中,
只是简单地进行反序列化。
 
values是哪儿来的呢?它是定义于基类CustomPropertyType的一个protected字段。包含了构造函数传入的inputValues参数。
 
inputValues的值又是哪来的呢?换句话说,既然Entity只是一个接口,谁负责实例化Entity的SampleProperty属性,并传入SampleCustomProperty类的构造函函数需要的inputValues参数呢? 

我们注意到,EntityWithCustomProperty.SampleProperty属性包含一个CustomPropertyAttribute修饰,它的参数XmlServerConfig和XmlContactConfig表示,名为这两个名称的属性的值,将会被Entity的实现类用来构造成一个obejct[]数组,传递给SampleCustomPropertyType的构造函数。 

注意,NBear.Common.CustomPropertyAttribute的构造函数接受一个params string[]类型的参数,可以是任意数量的Property的名称。 

那么,这样一个SampleProperty属性,到底有什么用呢? 

让我们来看看测试代码: 

   [TestMethod]
   public void TestCustomPropertyMethod()
   {
       //provided the entity obj's value is read from database
       EntityWithCustomProperty obj = EntityFactory<EntityWithCustomProperty>.CreateObject();
       SampleServerConfig sc = new SampleServerConfig();
       sc.ServerAddress = "127.0.0.1";
       sc.ServerPort = 8888;
       obj.XmlServerConfig = SerializeHelper.Serialize(sc);
       SampleContactConfig cc = new SampleContactConfig();
       cc.WorkPhone = "110";
       cc.HomePhone = "119";
       obj.XmlContactConfig = SerializeHelper.Serialize(cc);
 
       //now we can use Custom Property like following
       Assert.AreEqual(obj.SampleProperty.ServerConfig.ServerAddress, "127.0.0.1");
       Assert.AreEqual(obj.SampleProperty.ServerConfig.ServerPort, 8888);
       Assert.AreEqual(obj.SampleProperty.ContactConfig.WorkPhone, "110");
       Assert.AreEqual(obj.SampleProperty.ContactConfig.HomePhone, "119");
   }
 
在这段测试代码中,我们先构造了一个obj对象,它是一个EntityWithCustomProperty实体的实例。我们假设,这个实例的数据是从数据库中读取的,ID,Name,
XmlServerConfig和XmlContactConfig属性分别对应于数据表中的字段值。XmlServerConfig和XmlContactConfig的值,分别是两组XML序列化文本。 

很显然,如果我们直接读取XmlServerConfig和XmlContactConfig属性,因为他们是XML,并不易于使用(XML也许还好,如果它是一组二进制压缩数据呢?)。但是如果我们调用SampleProperty属性,我们就能很方便的读取XML中的真实内容(XML被自动反序列化为SampleProperty属性实例中的值了)。

--

实体多根继承

NBear中的Entity因为是以接口形式定义的,所以,它可以很方便的支持实体继承关系,甚至支持多根继承。也就是从2个以上的基类(接口)继承。 

NBear支持两种类型的非常自然的继承关系映射:单表继承体系方式一实体一表方式。 

让我们先看第一个例子: 

       [Table("AllInOneTable")]
       public interface Parent : IEntity
       {
           [PrimaryKey]
           int ID { get; }
           string Name { get; set; }
       }
 
       [Table("AllInOneTable")]
       public interface AnotherParent : IEntity
       {
           [PrimaryKey]
           int ID { get; }
           int Age { get; set; }
       }
 
       [Table("AllInOneTable")]
       public interface Child : Parent, AnotherParent
       {
           [PrimaryKey]
           new int ID { get; set; }
           DateTime Birthday { get; set; }
       }
 
我们可以看到,在上例中,我们定义了两个基实体Parent和AnotherParent,Child实体同时从两个基类继承。注意,代码中加粗的行,如果多个不同的基接口包含相同名称的属性,代码会编译失败,此时,需要像这样使用new关键字来避免编译失败。 

再注意,在这个例子中,我们和数据库表是如何对应的呢? 

这里,我们采用的是,单表继承体系方式。也就是说,用一张单独的表,存储整个继承体系的类。注意每个实体都映射到AllInOneTable这个表。 

那么,是不是只能使用单表继承体系方式来映射继承关系的实体呢?当然不是(见是比较推荐,因为,该方案相对比较方便灵活)。 

我们也可以采用一实体一表方式。代码如下: 

       [Table("ParentTable")]
       public interface Parent : IEntity
       {
           [PrimaryKey]
           int ID { get; }
           string Name { get; set; }
       }
 
       [Table("AnotherParentTable")]
       public interface AnotherParent : IEntity
       {
           [PrimaryKey]
           int ID { get; }
           int Age { get; set; }
       }
 
       [Table("ChildTable")]
       public interface Child : Parent, AnotherParent
       {
           [PrimaryKey]
           new int ID { get; set; }
           DateTime Birthday { get; set; }
       }
 
这种方案下,每个实体对应一张表,每张表包含冗余的父类的数据。

posted @ 2006-08-09 16:38  Teddy's Knowledge Base  Views(4434)  Comments(24Edit  收藏  举报