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


    第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接口定义了执行某个具体的对象同数据库进行交互的接口,在这个接口的下面,扩展出针对各个数据库的具体的实现类。这个部分的结构可以用下面的图来表示:


  • 相关阅读:
    用场景来规划测试工作
    冲刺第二十天 到二十二天
    冲刺第十九天
    冲刺第十八天
    阅读《构建之法》第13-17章(包含读后感)
    冲刺第5,6天(5月25,26日)
    冲刺第四天(2天合一起当一篇随笔,明天会在这篇里继续更新)
    冲刺第二天
    作业5.2
    作业5.1
  • 原文地址:https://www.cnblogs.com/twttafku/p/943603.html
Copyright © 2020-2023  润新知