构建面向对象的应用软件系统框架 第5章

 

第5章          设计一个O/R Mapping框架

在本章中,我们将设计一个可用的O/R Mapping框架,来详细讨论一下在O/R Mapping中可能用到的一些技术,以及一些问题的处理对策。

整个框架,我们会使用C#语言来编写,并且,会以Websharp框架作为实际的例子,关于Websharp框架的信息和源代码,可以从www.websharp.org 下载。

     51封装数据库访问层

一个好的O/R Mapping框架,应当做到数据库无关性,这就要求对数据库的访问做一个封装,能够屏蔽不同数据库之间的差异,这样,在更换后台数据库的时候,能够不用重新修改代码。   

.Net中,微软提供的基础数据库访问技术是ADO.NetADO.NET 是基于 .NET 的应用程序的数据访问模型。可以使用它来访问关系数据库系统(如 SQL Server 2000Oracle)和其他许多具有 OLE DB ODBC 提供程序的数据源。在某种程度上,ADO.NET 代表 ADO 技术的最新进展。不过,ADO.NET 引入了一些重大变化和革新,旨在解决 Web 应用程序的松耦合特性以及在本质上互不关联的特性。

ADO.NET 依赖于 .NET 数据提供程序的服务。这些提供程序提供对基础数据源的访问,并且包括五个主要对象(ConnectionCommandDataSetDataReader DataAdapter)。

目前,ADO.NET 随附了两类提供程序:Bridge提供程序和Native提供程序。通过Bridge 提供程序(如那些为 OLE DB ODBC 提供的提供程序),可以使用为以前的数据访问技术设计的数据库。Native 提供程序(如 SQL Server Oracle 提供程序)通常能够提供性能方面的改善,部分原因在于少了一个抽象层。

Ø         SQL Server .NET 数据提供程序。这是一个用于 Microsoft SQL Server 7.0 和更高版本数据库的提供程序。它被进行了优化以便访问 SQL Server,并且它通过使用 SQL Server 的本机数据传输协议来直接与 SQL Server 进行通讯。 当连接到 SQL Server 7.0 SQL Server 2000 时,应当始终使用该提供程序。

Ø         Oracle .NET 数据提供程序。用于 Oracle .NET 框架数据提供程序通过 Oracle 客户端连接软件支持对 Oracle 数据源的数据访问。该数据提供程序支持 Oracle 客户端软件版本 8.1.7 及更高版本。

Ø         OLE DB .NET 数据提供程序。这是一个用于 OLE DB 数据源的托管提供程序。它的效率要比 SQL Server .NET 数据提供程序稍微低一些,因为它在与数据库通讯时通过 OLE DB 层进行调用。请注意,该提供程序不支持用于开放式数据库连接 (ODBC) OLE DB 提供程序 MSDASQL。对于 ODBC 数据源,请改为使用 ODBC .NET 数据提供程序(稍后将加以介绍)。

Ø         ODBC .NET 数据提供程序。用于 ODBC .NET 框架数据提供程序使用本机 ODBC 驱动程序管理器 (DM) 来支持借助于 COM 互操作性进行的数据访问。还有其他一些目前正处于测试阶段的 .NET 数据提供程序。

与各个 .NET 数据提供程序相关联的类型(类、结构、枚举等)位于其各自的命名空间中:

Ø         System.Data.SqlClient包含 SQL Server .NET 数据提供程序类型。

Ø         System.Data.OracleClient:包含 Oracle .NET 数据提供程序。

Ø         System.Data.OleDb包含 OLE DB .NET 数据提供程序类型。

Ø         System.Data.Odbc:包含 ODBC .NET 数据提供程序类型。

Ø         System.Data:包含独立于提供程序的类型,如 DataSet DataTable

在各自的关联命名空间内,每个提供程序都提供了对 ConnectionCommandDataReader DataAdapter 对象的实现。SqlClient 实现的前缀为“Sql”,而 OleDb 实现的前缀为“OleDb”。例如,Connection 对象的 SqlClient 实现是 SqlConnection,而 OleDb 实现则为 OleDbConnection。同样,DataAdapter 对象的两个实现分别为 SqlDataAdapter OleDbDataAdapter 

为了屏蔽不同数据库之间的差异,我们首先要设计数据库访问的接口。把这个接口名为DataAccess,定义如下:

     public interface DataAccess

     {

         #region Support Property & Method

         DatabaseType DatabaseType{get;}

         IDbConnection DbConnection{get;}

         IDbTransaction BeginTransaction();

         void Open();

         void Close();

         bool IsClosed{get;}

 

         #endregion

 

         #region ExecuteNonQuery

 

         int ExecuteNonQuery(CommandType commandType, string commandText);

         int ExecuteNonQuery(string commandText);

         int ExecuteNonQuery(string commandText, QueryParameterCollection commandParameters);

         int ExecuteNonQuery(CommandType commandType, string commandText, QueryParameterCollection commandParameters);

 

         #endregion ExecuteNonQuery

          //……因篇幅的原因,这里没有列出所有的方法,关于其他方法的定义请参见源代码。

}

 

在这个接口之下,定义AbstractDataAccsee类,实现一些公用的数据方法,在AbstractDataAccsee类之下,再扩展出各个具体的DataAccsee实现类。整个结构可以用下面的图(3.1)来表示:


3.1

DataAccsee类的代码片断如下:

public abstract class AbstractDataAccess : DataAccess

{

     #region DataAccess

 

     #region Support Property & method

     public abstract DatabaseType DatabaseType{get;}   

     public abstract IDbConnection DbConnection{get;}

    

     public void Close()

     {

         this.DbConnection.Close();

     }

     public void Open()

     {

         if(this.DbConnection.State.Equals(ConnectionState.Closed))

         this.DbConnection.Open();

     }

     ……

 

     #endregion Support Property & method

 

     #region ExecuteNonQuery

     public int ExecuteNonQuery(CommandType commandType, string commandText)

     {

         return this.ExecuteNonQuery(commandType, commandText, null);

     }

 

     public int ExecuteNonQuery(string commandText)

     {

         return this.ExecuteNonQuery(CommandType.Text, commandText, null);

     }

     public int ExecuteNonQuery(string commandText, QueryParameterCollection commandParameters)

     {

         return this.ExecuteNonQuery(CommandType.Text, commandText, commandParameters);

     }

 

     public abstract int ExecuteNonQuery(CommandType commandType, string commandText, QueryParameterCollection commandParameters);

     #endregion ExecuteNonQuery

 

     protected void SyncParameter(QueryParameterCollection commandParameters)

     {

         if((commandParameters!=null) && (commandParameters.Count>0) )

         {

         for(int i=0;i<commandParameters.Count;i++)

         {

              commandParameters[i].SyncParameter();         

         }

         }

     }

}

然后,我们可以实现具体的数据库访问的方法。例如,SQL Server的数据库访问类可以实现如下:

public sealed class MSSqlDataAccess : AbstractDataAccess

{

     #region Constructor

     public MSSqlDataAccess(SqlConnection conn)

     {

         this.m_DbConnection=conn;

     }

 

     public MSSqlDataAccess(string connectionString)

     {

         this.m_DbConnection=new SqlConnection(connectionString);

     }

     #endregion

    

     #region DataAccess

 

     #region Support Property & method

     public override DatabaseType DatabaseType

     {

         get{return DatabaseType.MSSQLServer;}

     }   

     private SqlConnection m_DbConnection;

     public override IDbConnection DbConnection

     {

         get

         {

              return m_DbConnection;

         }

     }

    

     private SqlTransaction trans=null;

     public override IDbTransaction BeginTransaction()

     {

         trans=m_DbConnection.BeginTransaction();

         return trans;

     }

 

     #endregion Support Property & method

 

     #region ExecuteNonQuery

     public override int ExecuteNonQuery(CommandType commandType, string commandText, QueryParameterCollection commandParameters)

     {

         SqlCommand cmd=new SqlCommand();

         PrepareCommand(cmd,commandType, commandText,commandParameters);

         int tmpValue=cmd.ExecuteNonQuery();

         SyncParameter(commandParameters);

         cmd.Parameters.Clear();

         return tmpValue;

     }

     #endregion ExecuteNonQuery

 

     #region ExecuteDataSet

 

     public override DataSet ExecuteDataset(CommandType commandType, string commandText, QueryParameterCollection commandParameters,DataSet ds,string tableName)

     {

         SqlCommand cmd=new SqlCommand();

         PrepareCommand(cmd,commandType, commandText,commandParameters);

 

         SqlDataAdapter da=new SqlDataAdapter(cmd);

         if(Object.Equals(tableName,null) || (tableName.Length<1))

              da.Fill(ds);

         else

              da.Fill(ds,tableName);

 

         SyncParameter(commandParameters);

         cmd.Parameters.Clear();

         return ds;

     }

 

     #endregion ExecuteDataSet

 

     #region ExecuteReader 

     public override IDataReader ExecuteReader(CommandType commandType, string commandText, QueryParameterCollection commandParameters)

     {

         SqlCommand cmd=new SqlCommand();

         PrepareCommand(cmd,commandType, commandText,commandParameters);

         SqlDataReader dr=cmd.ExecuteReader();         

         SyncParameter(commandParameters);

         cmd.Parameters.Clear();

         return dr;

     }

     #endregion ExecuteReader   

 

     #region ExecuteScalar 

     public override object ExecuteScalar(CommandType commandType, string commandText, QueryParameterCollection commandParameters)

     {

         SqlCommand cmd=new SqlCommand();

         PrepareCommand(cmd,commandType, commandText,commandParameters);

         object tmpValue=cmd.ExecuteScalar();

         SyncParameter(commandParameters);

         cmd.Parameters.Clear();

         return tmpValue;

     }

     #endregion ExecuteScalar   

 

     #region ExecuteXmlReader   

     public override XmlReader ExecuteXmlReader(CommandType commandType, string commandText, QueryParameterCollection commandParameters)

     {

         SqlCommand cmd=new SqlCommand();

         PrepareCommand(cmd,commandType, commandText,commandParameters);

         XmlReader reader=cmd.ExecuteXmlReader();

         SyncParameter(commandParameters);

         cmd.Parameters.Clear();

         return reader;

     }

     #endregion ExecuteXmlReader

 

     #endregion

 

     private void PrepareCommand(SqlCommand cmd,CommandType commandType, string commandText, QueryParameterCollection commandParameters)

     {

         cmd.CommandType=commandType;

         cmd.CommandText=commandText;

         cmd.Connection=this.m_DbConnection;

         cmd.Transaction=trans;

         if((commandParameters!=null) && (commandParameters.Count>0) )

         {

              for(int i=0;i<commandParameters.Count;i++)

              {

                   commandParameters[i].InitRealParameter(DatabaseType.MSSQLServer);

                   cmd.Parameters.Add(commandParameters[i].RealParameter as SqlParameter);

              }

         }

     }

}

现在,我们已经有了数据库访问的接口和具体的实现类,为了管理这些类,并且提供可扩展性,我们需要创建一个类来提供获取具体实现类的方法,这个类就是Factory类。这个类很简单,主要的功能就是根据参数,判断使用什么数据库,然后,返回适当的DataAccess类。这是典型的Factory设计模式。关于设计模式的更多资料,可以参考《设计模式——可复用面向对象设计基础》一书。

这个类的定义如下:

     public sealed class DataAccessFactory

     {

         private DataAccessFactory(){}

         private static DatabaseProperty defaultDatabaseProperty;

         public static DatabaseProperty DefaultDatabaseProperty

         {

              get{return defaultDatabaseProperty;}

              set{defaultDatabaseProperty=value;}

         }

         public static DataAccess CreateDataAccess(DatabaseProperty pp)

         {

              DataAccess dataAccess;

              switch(pp.DatabaseType)

              {

                   case(DatabaseType.MSSQLServer):

                       dataAccess = new MSSqlDataAccess(pp.ConnectionString);

                       break;

                   case(DatabaseType.Oracle):

                       dataAccess = new OracleDataAccess(pp.ConnectionString);

                       break;

                   case(DatabaseType.OleDBSupported):

                       dataAccess = new OleDbDataAccess(pp.ConnectionString);

                       break;

                   default:

                       dataAccess=new MSSqlDataAccess(pp.ConnectionString);

                       break;

              }

              return dataAccess;

         }

         public static DataAccess CreateDataAccess()

         {

              return CreateDataAccess(defaultDatabaseProperty);

         }

     }

关于DatabasePropertyDatabaseType的定义,可以参见相关源代码。

数据访问功能的调用形式如下:

DataAccess dao=DataAccessFactory.CreateDataAccess(persistenceProperty);

db.Open();

db.需要的操作

db.Close();

当数据库发生变化的时候,只需要修改相应的DatabaseProperty参数,DataAccessFactory会根据参数的不同,自动调用相应的类,客户端不会感觉到变化,也不用去关心。这样,实现了良好的封装性。当然,前提是,你在编写程序的时候,没有用到特定数据库的特性,例如,Sql Server的专用函数。

   

     52设计映射

映射部分,完成对象和关系型数据库之间映射关系的表达。前面探讨过,在.Net环境中,可以使用Attribute来描述。在Websharp框架中,我们设计了以下Attribute来描述对象和关系型数据库之间的映射。

 

Ø         TableMapAttribute

这个Attribute描述对象和数据库表的映射关系,这个类有两个属性,TableName属性指明和某个类所对应的数据库表,PrimaryKeys用来描述表的主关键字。这个类的定义如下:

     [AttributeUsage(AttributeTargets.Class)]

     public class TableMapAttribute : Attribute

     {

         private string tableName;

         private string[] primaryKeys;

         public TableMapAttribute(string tableName,params string[] primaryKeys)

         {

              this.tableName = tableName;

              this.primaryKeys = primaryKeys;

         }

         public string TableName

         {

              get{return tableName;}

              set{tableName = value;}

         }

        

         public string[] PrimaryKeys

         {

              get{return primaryKeys;}

              set{primaryKeys = value;}

         }

     }

 

Ø         ColumnMapAttribute

这个Attribute描述对象属性和数据库中表的字段之间的映射关系,这个类有三个属性,ColumnName属性指明和某个属性所对应的字段,DbType属性指明数据库字段的数据类型,DefaultValue指明字段的默认值。这个类的定义如下:

     [AttributeUsage(AttributeTargets.Property)]

     public class ColumnMapAttribute : Attribute

     {

         private string columnName;

         private DbType dbtype;

         private object defaultValue;

         public ColumnMapAttribute(string columnName,DbType dbtype)

         {

              this.columnName = columnName;

              this.dbtype = dbtype;

         }

 

         public ColumnMapAttribute(string columnName,DbType dbtype,object defaultValue)

         {

              this.columnName = columnName;

              this.dbtype = dbtype;

              this.defaultValue = defaultValue;

         }

 

         public string ColumnName

         {

              get{return columnName;}

              set{columnName = value;}

         }

 

         public DbType DbType

         {

              get{return dbtype;}

              set{dbtype = value;}

         }

        

         public object DefaultValue

         {

              get{return defaultValue;}

              set{defaultValue = value;}

         }

     }

 

Ø         ReferenceObjectAttribute

ReferenceObjectAttribute指示该属性是引用的另外一个对象,因此,在执行持久化操作的时候,需要根据参数进行额外的处理。默认情况下,当持久化实体对象的时候,ReferenceObjectAttribute指示的属性,不进行操作。这个类有三个属性,ReferenceType指明所引用的对象的类型,PrimaryKeyForeignKey用来指明两个类之间进行关联的主键和外键。这个类的定义如下:

     [AttributeUsage(AttributeTargets.Property)]

     public class ReferenceObjectAttribute : Attribute

     {

         private Type referenceType;

         private string primaryKey;

         private string foreignKey;

         public ReferenceObjectAttribute(Type referenceType,string primaryKey,string foreignKey)

         {

              this.referenceType = referenceType;

              this.primaryKey = primaryKey;

              this.foreignKey = foreignKey;

         }

 

         public ReferenceObjectAttribute(){}

 

         public Type ReferenceType

         {

              get{return referenceType;}

              set{referenceType = value;}

         }

 

         public string PrimaryKey

         {

              get{return primaryKey;}

              set{primaryKey = value;}

         }

 

         public string ForeignKey

         {

              get{return foreignKey;}

              set{foreignKey = value;}            

         }

     }

 

 

Ø         SubObjectAttribute

SubObjectAttribute指示该属性是引用的是子对象,因此,在执行持久化操作的时候,需要根据参数进行额外的处理。默认情况下,当持久化实体对象的时候,SubObjectAttribute指示的属性,不进行操作。

这个类的定义如下:

     [AttributeUsage(AttributeTargets.Property)]

     public class SubObjectAttribute : Attribute

     {

        private Type subObjectType;

         private string primaryKey;

         private string foreignKey;

        public SubObjectAttribute(Type subObjectType, string primaryKey, string foreignKey)

         {

            this.subObjectType = subObjectType;

              this.primaryKey = primaryKey;

              this.foreignKey = foreignKey;

         }

 

         public SubObjectAttribute(){}

 

        public Type SubObjectType

         {

            get { return subObjectType; }

            set { subObjectType = value; }

         }

 

         public string PrimaryKey

         {

              get{return primaryKey;}

              set{primaryKey = value;}

         }

 

         public string ForeignKey

         {

              get{return foreignKey;}

              set{foreignKey = value;}            

         }

     }

 

Ø         AutoIncreaseAttribute

AutoIncreaseAttribute指示该属性是自动增长的。自动增长默认种子为

这个类的定义如下:

     [AttributeUsage(AttributeTargets.Property)]

     public class AutoIncreaseAttribute : Attribute

     {

         private int step = 1;

 

         public AutoIncreaseAttribute(){}

 

         public AutoIncreaseAttribute(int step)

         {

              this.step = step;

         }

 

         public int Step

         {

              get{return step;}

              set{step = value;}

         }

     }

 

设计好映射的方法后,我们就可以来定义实体类以及同数据库之间的映射。下面是一个例子:

    //订单类别

    [TableMap("OrderType","ID")]

    public class OrderType

    {

        private int m_ID;

        private string m_Name;

 

        [ColumnMap("ID",DbType.Int32)]

        public int ID

        {

            get { return m_ID; }

            set { m_ID = value; }

        }

        [ColumnMap("Name", DbType.String)]

        public string Name

        {

            get { return m_Name; }

            set { m_Name = value; }

        }

    }

 

//订单

[TableMap("Order", "OrderID")]

    public class Order

    {

        private int m_OrderID;

        private OrderType m_OrderType;

        private string m_Title;

        private DateTime m_AddTime;

        private bool m_IsSigned;

        private List<OrderDetail> m_Details;

 

        [ColumnMap("OrderID",DbType.Int32)]

        [AutoIncrease]

        public int OrderID

        {

            get { return m_OrderID; }

            set { m_OrderID = value; }

        }

        [ReferenceObject(typeof(OrderType),"ID","TypeID")]

        [ColumnMap("TypeID",DbType.String)]

        public OrderType OrderType

        {

            get { return m_OrderType; }

            set { m_OrderType = value; }

        }

        [ColumnMap("Title", DbType.String)]

        public string Title

        {

            get { return m_Title; }

            set { m_Title = value; }

        }

        [ColumnMap("AddTime", DbType.DateTime)]

        public DateTime AddTime

        {

            get { return m_AddTime; }

            set { m_AddTime = value; }

        }

        [ColumnMap("AddTime", DbType.Boolean)]

        public bool IsDigned

        {

            get { return m_IsSigned; }

            set { m_IsSigned = value; }

        }

        [SubObject(typeof(OrderDetail),"OrderID","OrderID")]

        public List<OrderDetail> Details

        {

            get { return m_Details; }

            set { m_Details = value; }

        }

    }

 

    //订单明细

    public class OrderDetail

    {

        private int m_DetailID;

        private int m_OrderID;

        private string m_ProductName;

        private int m_Amount;

 

        [ColumnMap("ID", DbType.Int32)]

         [AutoIncrease]

        public int DetailID

        {

            get { return m_DetailID; }

            set { m_DetailID = value; }

        }

        [ColumnMap("OrderID", DbType.Int32)]

        public int OrderID

        {

            get { return m_OrderID; }

            set { m_OrderID = value; }

        }

        [ColumnMap("ProductName", DbType.String)]

        public string ProductName

        {

            get { return m_ProductName; }

            set { m_ProductName = value; }

        }

        [ColumnMap("Amount", DbType.Int32)]

        public int Amount

        {

            get { return m_Amount; }

            set { m_Amount = value; }

        }

    }

Order

        [ReferenceObject(typeof(OrderType),"ID","TypeID")]

        [ColumnMap("TypeID",DbType.String)]

        public OrderType OrderType

        {

            get { return m_OrderType; }

            set { m_OrderType = value; }

        }

这段代码表明,OrderType这个属性,引用了OrderType这个对象,同OrderType相关联的,是OrderType的主键IDOrder的外键TypeID

 

        [SubObject(typeof(OrderDetail),"OrderID","OrderID")]

        public List<OrderDetail> Details

        {

            get { return m_Details; }

            set { m_Details = value; }

        }

这段代码表明,Details这个属性,由子对象OrderDetail的集合组成,其中,两个对象通过Order类的OrderID主键和OrderDetail的外键OrderID相关联。

 

有了以上的类结构,我们可以为他们生成相应的数据库操作的SQL语句。在上面的三个对象中,分别对应的SQL语句是(以SQL Server为例):

OrderType

INSERT INTO OrderType(ID,Name) VALUES(@ID,@NAME)

UPDATE OrderType SET Name=@Name Where ID=@ID

DELETE FROM OrderType Where ID=@ID

 

Order

INSERT INTO Order(TypeID,Title,AddTime,IsSigned) VALUES (@TypeID, @Title, @AddTime, @IsSigned) ; SELECT @OrderID=@@IDENTITY  其中@OrderID为传出参数

 

UPDATE Order SET TypeID=@TypeID, Title=@Title, AddTime=@AddTime, IsSigned=@IsSigned WHERE OrderID=@OrderID

 

DELETE FROM Order WHERE OrderID=@OrderID

 

OederDetail

INSERT INTO OederDetail(OrderID,ProductName,Amount) VALUES (@OrderID, @ProductName, @Amount); SELECT @ID=@@IDENTITY   其中@ID为传出参数

 

UPDATE OederDetail SET OrderID=@OrderID, ProductName=@ProductName, Amount=@Amount WHERE ID=@ID

 

DELETE FROM OederDetail WHERE ID=@ID

 

 

     53 对继承的支持

Websharp框架在设计的时候,要求能够支持面向对象语言中的继承。前面已经讨论过,在O/R Mapping框架中,一般说来,有三种继承模式:ONE_INHERITANCE_TREE_ONE_TABLEONE_INHERITANCE_PATH_ONE_TABLEONE_CLASS_ONE_TABLE。在Websharp框架中可以实现对这三种模式的支持。我们依然以前面的第三章第3.2节的例子为例:

 

ONE_INHERITANCE_TREE_ONE_TABLE

这种映射模式将具有相同父类的所有类都映射到一个数据库表中。数据库结构如下图:


Websharp中,只需要对每个类都指明具有相同值的TableMap特性就可以了。如下面的代码:

    [TableMap("Table1", "Property1")]

    public class Parent

    {

        private string property1;

        private string property2;

 

        [ColumnMap("Column1", DbType.String)]

        public string Property1

        {

            get { return property1; }

            set { property1=value; }

        }

 

        [ColumnMap("Column2", DbType.String)]

        public string Property2

        {

            get { return property2; }

            set { property2 = value; }

        }

    }

 

    [TableMap("Table1", "Property1")]

    public class Child1 : Parent

    {

        private string property3;

 

        [ColumnMap("Column3", DbType.String)]

        public string Property3

        {

            get { return property3; }

            set { property3 = value; }

        }

    }

 

    [TableMap("Table1", "Property1")]

    public class Child2 : Parent

    {

        private string property4;

 

        [ColumnMap("Column4", DbType.String)]

        public string Property4

        {

            get { return property4; }

            set { property4 = value; }

        }

    }

 

此时,当按照如下的代码初始化一个Child1对象,

    Child1 c1 = new Child1();

    c1.Property1 = "P11";

    c1.Property2 = "P12";

    c1.Property3 = "P13";

 

并保存到数据库中的时候,数据库中的记录应该是:

Column1

Column2

Column3

Column4

P11

P12

P13

NULL

 

如果按照如下的代码初始化一个Child2对象:

    Child2 c1 = new Child2();

    c2.Property1 = "P21";

    c2.Property2 = "P22";

    c2.Property4 = "P24";

 

并保存到数据库中的时候,数据库中的记录应该是:

Column1

Column2

Column3

Column4

P21

P22

NULL

P24

 

 

ONE_INHERITANCE_PATH_ONE_TABLE

这种映射模式将一个继承路径映射到一个表,这种情况下的数据库的结构是:

 

这种情况下,实际上Parent类并不映射到实际的表,Child1Child2类分别映射到Child1Child2表。因此,在这种情况下,需要把Parent类的TableMap特性设置为Null,而Child1Child2类的TableMap特性分别设置为Child1Child2,代码如下面所示:

    [TableMap(null, "Property1")]

    public class Parent

    {

        private string property1;

        private string property2;

 

        [ColumnMap("Column1", DbType.String)]

        public string Property1

        {

            get { return property1; }

            set { property1=value; }

        }

 

        [ColumnMap("Column2", DbType.String)]

        public string Property2

        {

            get { return property2; }

            set { property2 = value; }

        }

    }

 

    [TableMap("Child1", "Property1")]

    public class Child1 : Parent

    {

        private string property3;

 

        [ColumnMap("Column3", DbType.String)]

        public string Property3

        {

            get { return property3; }

            set { property3 = value; }

        }

    }

 

    [TableMap("Child2", "Property1")]

    public class Child2 : Parent

    {

        private string property4;

 

        [ColumnMap("Column4", DbType.String)]

        public string Property4

        {

            get { return property4; }

            set { property4 = value; }

        }

    }

此时,当按照如下的代码初始化一个Child1对象,

    Child1 c1 = new Child1();

    c1.Property1 = "P11";

    c1.Property2 = "P12";

    c1.Property3 = "P13";

 

并保存到数据库中的时候,数据库中应该只在Child1表中添加下面的数据:

Column1

Column2

Column3

P11

P12

P13

如果保存的是一个Child2对象,那么,应该只在Child2表中添加数据。Child1表和Child2表是互相独立的,并不会互相影响。

 

ONE_CLASS_ONE_TABLE

这种映射模式将每个类映射到对应的一个表,对于上面的类结构,数据库的结构是:

 

 

这种映射模式,我们只需要分别对每个类设定各自映射的表就可以了。代码如下面所示:

    [TableMap("Parent", "Property1")]

    public class Parent

    {

        private string property1;

        private string property2;

 

        [ColumnMap("Column1", DbType.String)]

        public string Property1

        {

            get { return property1; }

            set { property1=value; }

        }

 

        [ColumnMap("Column2", DbType.String)]

        public string Property2

        {

            get { return property2; }

            set { property2 = value; }

        }

    }

 

    [TableMap("Child1", "Property1")]

    public class Child1 : Parent

    {

        private string property3;

 

        [ColumnMap("Column3", DbType.String)]

        public string Property3

        {

            get { return property3; }

            set { property3 = value; }

        }

    }

 

    [TableMap("Child2", "Property1")]

    public class Child2 : Parent

    {

        private string property4;

 

        [ColumnMap("Column4", DbType.String)]

        public string Property4

        {

            get { return property4; }

            set { property4 = value; }

        }

    }

此时,当按照如下的代码初始化一个Child1对象,

    Child1 c1 = new Child1();

    c1.Property1 = "P11";

    c1.Property2 = "P12";

    c1.Property3 = "P13";

 

并保存到数据库中的时候,数据库中的记录应该是:

Parent表:

Column1

Column2

P11

P12

Child1表:

Column1

Column3

P11

P13

 

同样的,如果保存的是一个Child2对象,那么,将在Parent表和Child2表中添加记录。

 

     54设计对象操纵框架   

由于我们采用了对象同操作分开的方式,因此,需要设计一个统一的接口来完成对对象的操纵。为了使用的方便性,我们尽量设计少的接口。我们设计以下三个主要接口:

1、  PersistenceManager:这类完成所有对对象的增加、修改以及删除的操作,并提供简单的查询功能。

2、  Query:这个接口完成对对象的查询功能。

3、  Transaction:这个接口负责处理事务。

另外,为了描述对象的状态,定义了EntityState枚举。为了简单化,这里只定义了四个状态,如下面的定义:

public enum EntityState{Transient,New,Persistent,Deleted}

 

对上面几个接口的说明分别如下:

 

Ø         PersistenceManager

这个接口的定义如下:

     public enum PersistOptions{SelfOnly,IncludeChildren,IncludeReference,Full}

     public interface PersistenceManager : IDisposable

     {

         void Close();

         bool IsClosed{get;}

         Transaction CurrentTransaction{  get;}

         bool IgnoreCache{get;set;}

 

         void PersistNew(object entity);

         void PersistNew(object entity,PersistOptions options);

 

         void Persist(object entity);

         void Persist(object entity,PersistOptions options);

         void Persist(object entity,params string[] properties);

         void Persist(object entity,PersistOptions options,params string[] properties);

 

         void Delete(object entity);

         void Delete(object entity,PersistOptions options);

 

         void Attach(object entity);

         void Attach(object entity,PersistOptions options);

 

         void Reload(object entity);

         void Reload(object entity,PersistOptions options);

 

         void Evict (object entity);

         void EvictAll (object[] pcs);

         void EvictAll (ICollection pcs);

         void EvictAll ();

 

         object FindByPrimaryKey(Type entityType,object id);

         object FindByPrimaryKey(Type entityType,object id,PersistOptions options);

         T FindByPrimaryKey<T>(object id);

         T FindByPrimaryKey<T>(object id, PersistOptions options);

 

         object GetReference(object entity);

         object GetReference(object entity,Type[] parents);

         object GetChildren(object entity);

         object GetChildren(object entity,Type[] children);

 

         EntityState GetState(object entity);

         ICollection GetManagedEntities();

         bool Flush();

 

         Query NewQuery();

         Query NewQuery(Type entityType);

         Query NewQuery(Type entityType,string filter);

         Query NewQuery(Type entityType,string filter,QueryParameterCollection paramColletion);

 

        Query<T> NewQuery<T>();

        Query<T> NewQuery<T>(string filter);

        Query<T> NewQuery<T>(string filter, QueryParameterCollection paramColletion);

     }

 

对于这个接口的几个主要方法说明如下:

PersistNew方法将一个新的实体对象转换成可持续对象,这个对象在事务结束的时候,会被Insert到数据库中。调用这个方法后,该对象的状态为EntityState.New。如果一个对象的状态为EntityState.Persistent,那么,这个方法将抛出一个EntityIsPersistentException异常。

Persist方法将一个实体对象保存到数据库中。如果一个对象是Trasient的,则将其转换为EntityState.New状态。在事务结束的时候,会被Insert到数据库中;否则,其状态就是EntityState.Persist,就更新到数据库中。如果一个Trasient对象实际上已经存在于数据库中,由于Persist方法并不检查实际的数据库,因此,调用这个方法,将会抛出异常。这个时候,应该使用先使用Attach方法,然后调用PersistPersist方法主要用于已受管的对象的更新。

Delete方法删除一个对象。一个对象被删除后,其状态变成EntityState.Deleted,在事务结束的时候,会被从数据库中删除。 如果一个对象不是持久的,那么,这个方法将抛出异常。

Attach方法将一个对象标记为可持续的。如果这个对象已经存在于实际的数据库中,那么,这个对象的状态就是EntityState.Persistent,否则,这个对象的状态就是EntityState.New

Reload方法重新从数据库中载入这个对象,这意味着重新给对象的各个属性赋值。

Evict方法从缓存中把某个对象移除。

FindByPrimaryKey方法根据主键查找某个对象,如果主键是多个字段的,主键必须是PrimaryKey数组,否则抛出异常。

 

Ø         Query

这个接口的定义如下:

     public interface Query

     {

         Type EntityType{get;set;}

         string EntityTypeName{get;set;}   

         string Filter{get;set;}   

         QueryParameterCollection Parameters{get;set;}

         string Ordering{get;set;} 

         bool IgnoreCache{get;set;}  

         PersistOptions Options { get;set;}

 

        ICollection QueryObjects();

        DataSet QueryDataSet();

        object GetChildren(object entity);

        object GetChildren(DataSet dst);

        object GetChildren(object entity, Type[] children);

        object GetChildren(DataSet entity, Type[] children);

 

        object GetReference(object entity);

        object GetReference(DataSet entity);

        object GetReference(object entity, Type[] parents);

        object GetReference(DataSet entity, Type[] parents);

 

         PersistenceManager PersistenceManager{get;}

 

         bool IsClosed{get;}

         void Close ();

         void Open();

     }

 

    public interface Query<T> : Query

    {

        new ICollection<T> QueryObjects();

    }

Query接口的主要使用方法,是设定需要查询的对象的类型,以及过滤条件,然后执行QueryObjects方法,就可以得到相应的复合条件的对象。

Ø         Transaction

这个接口主要用于处理事务,提供的功能比较简单,包括事务的开始、提交以及回滚三个主要功能。这个接口的定义如下:

     public interface Transaction

     {

         void Begin();

         void Commit();

         void Rollback();

         PersistenceManager PersistenceManager{get;}

     }

定义好了接口,下面准备实现。这将在下面的小节中描述。

下面的例子展示了一个利用Websharp框架保存一个Order对象的过程:

    DatabaseProperty dbp = new DatabaseProperty();

    dbp.DatabaseType = DatabaseType.MSSQLServer;

    dbp.ConnectionString = "Server=127.0.0.1;UID=sa;PWD=sa;Database=WebsharpTest;";

PersistenceManager pm = PersistenceManagerFactory.Instance().Create(dbp);

 

    Order o = new Order();

    o.OrderType = new OrderType(3, "音响");

    o.OrderID = 3;

    o.Title = "SecondOrder";

    o.IsDigned = false;

    o.AddTime = DateTime.Now;

    o.Details = new List<OrderDetail>(2);

 

    for (int j = 1; j < 3; j++)

    {

        OrderDetail od= new OrderDetail();

        od.OrderID = 3;

        od.ProductID = j;

        od.Amount = j;

        o.Details.Add(od);

    }

 

pm.PersistNew(o, PersistOptions.IncludeChildren);

    pm.Flush();

pm.Close();

 

55实现对象操纵框架   

前面,我们已经定义好了O/R Mapping的基本框架,下面,我们来具体讨论实现这个框架需要的一些主要工作。

在实现中,以下几个方面是比较主要的:

Ø         MetaData

Ø         StateManager

Ø         SqlGenerator

Ø         IEntityOperator

 

MetaData用来记录对象和数据库之间映射的元数据信息,包括两个部分的元数据:

1、  对象和表映射的信息

2、  对象属性和字段映射的信息。

关于MetaData的定义可以参见Websharp的源代码。

MetaData数据,由专门的类来进行解析,并进行缓存处理。在Websharp中,由MetaDataManager来完成这个任务。MetaDataManager通过反射,读取实体类的信息,来得到MetaData数据。下面的代码片断演示了这个过程的主要内容。

首先,ParseMetaData方法读取类的信息:

private static MetaData ParseMetaData(Type t, DatabaseType dbType)

{   

    MetaData m = new MetaData();

    m.ClassName = t.Name;                   //类名

    m.EntityType = t;                       //实体类的类型

    m.MapTable = GetMappedTableName(t);     //实体类映射的表

    m.PrimaryKeys = GetKeyColumns(t);       //主键

    if (m.PrimaryKeys.Length > 1)

        m.IsMultiPrimaryKey = true;

    else

        m.IsMultiPrimaryKey = false;

……

}

然后,读取每个字段的信息。这个部分的代码比较长,下面,只列出部分代码:

    PropertyInfo[] pinfos = t.GetProperties();

    m.FieldMetaDatas = new Dictionary<string,FieldMetadata>(pinfos.Length);

 

    foreach (PropertyInfo pinfo in pinfos)

    {

        FieldMetadata fd = new FieldMetadata();

 

        fd.PropertyName = pinfo.Name;

        ColumnMapAttribute cattr = Attribute.GetCustomAttribute(pinfo, typeof(ColumnMapAttribute)) as ColumnMapAttribute;

        if(!Object.Equals(null,cattr))

        {

            fd.ColumnName = cattr.ColumnName;

            fd.DbType = cattr.DbType;

            fd.DefaultValue = cattr.DefaultValue;

        }

        else

        {

            fd.ColumnName = fd.PropertyName ;

            fd.DbType = DbType.String;

            fd.DefaultValue = String.Empty;                  

        }

……

}

最后,根据映射信息,构建同数据库进行交互时候的SQL语句。O/R Mapping框架的最后操作,还是回归到根据SQL语句来进行对数据库的操作。

     SqlGenerator sg = SqlGenerator.Instance(dbType);

     m.SelectSql = sg.GenerateSql(t, OperationType.SelectByKey);

SQL语句的具体构建,由SqlGenerator来完成。

SqlGenerator是一个抽象类,定义了构建同数据库进行交互的方法接口。在这个抽象类的下面,根据不同的数据库,扩展出针对不同数据库的SQL语句生成器。例如,一个SQL ServerSqlGenerator可以这样来生成插入一条记录需要的SQL语句:

    private SqlStruct GenerateInsertSql(Type entityType)

    {

        string autoP;

        bool autoInscrease = MetaDataManager.GetAutoInscreaseProperty(entityType, out autoP);

        List<string> lcolumns = MetaDataManager.GetDbColumns(entityType);

        string[] parameters = new string[lcolumns.Count];

        ParamField[] paramField = new ParamField[lcolumns.Count];

 

        if (autoInscrease)

        {

            lcolumns.Remove(autoP);

        }

        string[] columns = lcolumns.ToArray();

 

        for (int i = 0; i < columns.Length; i++)

        {

            parameters[i] = "@" + columns[i];

            paramField[i] = new ParamField(parameters[i], columns[i]);

        }

        if (autoInscrease)

        {

            parameters[parameters.Length-1] = "@" + autoP;

            paramField[parameters.Length-1] = new ParamField(parameters[parameters.Length - 1], autoP);

        }

 

        string tableName = MetaDataManager.GetMappedTableName(entityType);

        StringBuilder strSql = new StringBuilder("INSERT INTO ").Append(tableName).Append("(").Append(string.Join(",", columns)).Append(") VALUES(").Append(string.Join(",", parameters)).Append(")");

        if (autoInscrease)

        {

            strSql.Append(";SELECT @").Append(autoP).Append("=@@IDENTITY");

        }

        return new SqlStruct(strSql.ToString(),paramField);

    }

 

前面的章节讨论过对象的状态问题。在Websharp中,因为采用了普通的类就可以持久化的操作的方式,因此,需要另外的机制来管理对象的状态。

Websharp中,为了简化,只定义了四种对象的状态,分别是TransientNewPersistentDeleted,定义如下:

public enum EntityState{Transient,New,Persistent,Deleted}

在实现中,定义了StateManager类来管理对象的状态,这个类的定义如下:

    public class StateManager

    {

        public StateManager(object entity)

        {

            this.m_Entity = entity;

        }

        public StateManager(object entity,EntityState state)

        {

            this.m_Entity = entity;

            this.m_State = state;

        }

        private object m_Entity;

        public object Entity

        {

            get { return m_Entity; }

            set { m_Entity = value; }

        }

 

        private EntityState m_State;

        public EntityState State

        {

            get { return m_State; }

            set { m_State = value; }

        }

    }

PersistenceManager里面,持久化一个对象的时候,如果这个对象不是受管理的,则PersistenceManager会给这个对象分配一个StateManager。例如,当对一个对象执行PersistNew操作的时候,PersistenceManager将首先检查这个对象是否是受管理的,如果不是,则为这个对象分配一个StateManager,并且其状态为EntityState.New,然后,将这个对象添加到待操作列表中,在执行Flush方法的时候,会对这个对象执行一个新增的操作。代码如下:

public void PersistNew(object entity, PersistOptions options)

{

    //首先,检查这个对象是否已经是受管理的对象

    StateManager smanager;

    if (IsManagedBySelf(entity,out smanager))

    {

        throw new EntityIsPersistentException();

    }

 

    //将对象标记为受管理的,并且状态是EntityState.New

    smanager = new StateManager(entity,EntityState.New);

    stateManagers.Add(smanager);

 

    //添加到操作列表中

    opEntityList.Add(new OperationInfo(smanager,options));

}

最后,在执行Flush方法的时候,PersistenceManager会把所有的对象的变化反应到数据库中。

foreach (OperationInfo opInfo in opEntityList)

{

   IEntityOperator op = EntityOperatorFactory.CreateEntityOperator(dao, opInfo.StateManager.State);

    op.Execute(opInfo.StateManager.Entity, dao);

     CacheProxy.CacheEntity(opInfo.StateManager.Entity);

}

可以看到,具体的对数据库的操作,通过IEntityOperator接口来完成。IEntityOperator接口定义了执行某个具体的对象同数据库进行交互的接口,在这个接口的下面,扩展出针对各个数据库的具体的实现类。这个部分的结构可以用下面的图来表示:





posted on 2005-11-29 14:40  孙策  阅读(946)  评论(1编辑  收藏  举报