关闭

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

689人阅读 评论(0) 收藏 举报

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

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:55794次
    • 积分:715
    • 等级:
    • 排名:千里之外
    • 原创:0篇
    • 转载:59篇
    • 译文:0篇
    • 评论:3条
    文章存档