您的位置:首页 > 移动开发

设计一个O/R Mapping框架

2008-09-23 22:25 337 查看
/article/6411834.html 在本章中,我们将设计一个可用的O/R Mapping框架,来详细讨论一下在O/R Mapping中可能用到的一些技术,以及一些问题的处理对策。整个框架,我们会使用C#语言来编写,并且,会以Websharp框架作为实际的例子,关于Websharp框架的信息和源代码,可以从www.websharp.org下载。

    5.1封装数据库访问层

一个好的O/R Mapping框架,应当做到数据库无关性,这就要求对数据库的访问做一个封装,能够屏蔽不同数据库之间的差异,这样,在更换后台数据库的时候,能够不用重新修改代码。 在.Net中,微软提供的基础数据库访问技术是ADO.Net。ADO.NET 是基于 .NET 的应用程序的数据访问模型。可以使用它来访问关系数据库系统(如 SQL Server 2000、Oracle)和其他许多具有 OLE DB 或 ODBC 提供程序的数据源。在某种程度上,ADO.NET 代表 ADO 技术的最新进展。不过,ADO.NET 引入了一些重大变化和革新,旨在解决 Web 应用程序的松耦合特性以及在本质上互不关联的特性。ADO.NET 依赖于 .NET 数据提供程序的服务。这些提供程序提供对基础数据源的访问,并且包括五个主要对象(Connection、Command、DataSet、DataReader 和 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。在各自的关联命名空间内,每个提供程序都提供了对 Connection、Command、DataReader 和 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.1DataAccsee类的代码片断如下:
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);    }    }
关于DatabaseProperty和DatabaseType的定义,可以参见相关源代码。数据访问功能的调用形式如下:
DataAccess dao=DataAccessFactory.CreateDataAccess(persistenceProperty);db.Open();db.需要的操作db.Close();
当数据库发生变化的时候,只需要修改相应的DatabaseProperty参数,DataAccessFactory会根据参数的不同,自动调用相应的类,客户端不会感觉到变化,也不用去关心。这样,实现了良好的封装性。当然,前提是,你在编写程序的时候,没有用到特定数据库的特性,例如,Sql Server的专用函数。   

    5.2设计映射

映射部分,完成对象和关系型数据库之间映射关系的表达。前面探讨过,在.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;}    }    }
? ReferenceObjectAttributeReferenceObjectAttribute指示该属性是引用的另外一个对象,因此,在执行持久化操作的时候,需要根据参数进行额外的处理。默认情况下,当持久化实体对象的时候,ReferenceObjectAttribute指示的属性,不进行操作。这个类有三个属性,ReferenceType指明所引用的对象的类型,PrimaryKey和ForeignKey用来指明两个类之间进行关联的主键和外键。这个类的定义如下:
    [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;}     }    }
? SubObjectAttributeSubObjectAttribute指示该属性是引用的是子对象,因此,在执行持久化操作的时候,需要根据参数进行额外的处理。默认情况下,当持久化实体对象的时候,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;}     }    }
? AutoIncreaseAttributeAutoIncreaseAttribute指示该属性是自动增长的。自动增长默认种子为这个类的定义如下:
    [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的主键ID和Order的外键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=@IDDELETE 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=@OrderIDDELETE 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=@IDDELETE FROM OederDetail WHERE ID=@ID

    5.3 对继承的支持

Websharp框架在设计的时候,要求能够支持面向对象语言中的继承。前面已经讨论过,在O/R Mapping框架中,一般说来,有三种继承模式:ONE_INHERITANCE_TREE_ONE_TABLE、ONE_INHERITANCE_PATH_ONE_TABLE和ONE_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";
并保存到数据库中的时候,数据库中的记录应该是:
Column1Column2Column3Column4
P11P12P13NULL
如果按照如下的代码初始化一个Child2对象:
    Child2 c1 = new Child2();    c2.Property1 = "P21";    c2.Property2 = "P22";    c2.Property4 = "P24";
并保存到数据库中的时候,数据库中的记录应该是:
Column1Column2Column3Column4
P21P22NULLP24
ONE_INHERITANCE_PATH_ONE_TABLE这种映射模式将一个继承路径映射到一个表,这种情况下的数据库的结构是: 

这种情况下,实际上Parent类并不映射到实际的表,Child1和Child2类分别映射到Child1和Child2表。因此,在这种情况下,需要把Parent类的TableMap特性设置为Null,而Child1和Child2类的TableMap特性分别设置为Child1和Child2,代码如下面所示:
    [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表中添加下面的数据:
Column1Column2Column3
P11P12P13
如果保存的是一个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表:
Column1Column2
P11P12
Child1表:
Column1Column3
P11P13
同样的,如果保存的是一个Child2对象,那么,将在Parent表和Child2表中添加记录。

    5.4设计对象操纵框架 

由于我们采用了对象同操作分开的方式,因此,需要设计一个统一的接口来完成对对象的操纵。为了使用的方便性,我们尽量设计少的接口。我们设计以下三个主要接口: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方法,然后调用Persist。Persist方法主要用于已受管的对象的更新。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();

5.5实现对象操纵框架 

前面,我们已经定义好了O/R Mapping的基本框架,下面,我们来具体讨论实现这个框架需要的一些主要工作。在实现中,以下几个方面是比较主要的:? MetaData? StateManager? SqlGenerator? IEntityOperatorMetaData用来记录对象和数据库之间映射的元数据信息,包括两个部分的元数据: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 Server的SqlGenerator可以这样来生成插入一条记录需要的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中,为了简化,只定义了四种对象的状态,分别是Transient,New,Persistent和Deleted,定义如下:
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接口定义了执行某个具体的对象同数据库进行交互的接口,在这个接口的下面,扩展出针对各个数据库的具体的实现类。这个部分的结构可以用下面的图来表示:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐