安忍不动如大地,静虑深思如秘藏
雄鹰虽然有时候飞的比鸡还低 但鸡永远也不可能飞到鹰的高度

使用泛型和反射,打造我们的完美实体基类

 

 

背景

 

我在开发的过程中,实体和数据库的访问是一直要接触和编写的代码,考虑到团队中初学者比较多,我一直希望有一种方式可以改善目前编码的困境:

ADO.Net的代码对于初学者来讲比较麻烦,需要他们更多的对ADO.Net有所了解。

将数据库的值为实体属性赋值是一个比较繁琐的过程,程序员容易厌倦这个重复工作,或数据类型,或属性名称的赋值错误。

对于一对多的实体模型开发中,程序员很容易的将一对多的实体对象相互的关联错误。

 

我们当然可以使用一些自动化的工具来辅助,或者采用类似Hibernate的框架或VS2008SP1的一些新功能来实现,但我们又需要在我们的实体中有些更灵活的控制。

 

因此作为开发团队的技术管理人员,我必须要设计一种更好的模式来解决以上的事项。

 

正文中,我们将逐步的阐述我的设计思路。

 

 

 

一、打造DataProvider

 

ADO.Net的封装已经有很多的实现了,但我总感觉那些实现还是没有透明化使用者对ADO.Net的了解。比如说很多人推崇的Enterprise LibraryDataAccess,我认为就是封装不够彻底。我理想中封装彻底的ADO.Net对象是,使用者不需要(或尽可能的少)了解任何,而DataAccess还是需要使用者直接的处理很多ADO.Net的对象。而我需要的ADO.Net的封装希望使用者,仅给予SQL命令,赋值参数,并获取结果即可。

 

 

1.1定义DataProvide

 

定义SqlDataProvider

/// <summary>

/// SQL数据提供者的实现

/// </summary>

public class SqlDataProvider : DataProviders.IDataProvider

{

 

}

 

 

1.2定义DataProviders.IDataProvider

 

DataProviders.IDataProvider是我定义的一个接口,我希望将来所有的数据提供者都能实现该接口,以便利用依赖倒置实现抽象工厂。

 

 

定义DataProviders.IDataProvider接口

public interface IDataProvider

{

    void AddParameters(string parname, Guid value);

    void AddParameters(string parname, long value);

    void AddParameters(string parname, string value);

    void AddParameters(string parname, string value, DataProviders.StringFamily dataType);

    void AddParameters(string parname, string value, DataProviders.StringFamily dataType, int size);

    void AddParameters(string parname, float value);

    void AddParameters(string parname, decimal value);

    void AddParameters(string parname, DateTime value);

    void AddParameters(string parname, DateTime value, DataProviders.DateFamily dataType);

    void AddParameters(string parname, int value);

    void AddParameters(string parname, object value);

    void AddParameters(string parname, System.Drawing.Bitmap value);

    void AddParameters(string parname, byte[] value);

    void AddParameters(string parname, byte[] value, DataProviders.ByteArrayFamily dataType);

    void AddParameters(string parname, bool value);

    void AddParameters(string parname, short value);

    void AddParameters(string parname, byte value);

    System.Data.CommandType CommandType { get; set; }

    string ConnectionString { get; }

    System.Data.DataSet ExecuteDataSet();

    System.Data.DataTable ExecuteDataTable();

    void ExecuteReader(ReadData readData);

    int ExecuteNonQuery();

    object ExecuteScalar();

    string SQL { get; set; }

}

 

public delegate void ReadData(System.Data.IDataReader dataReadre);

 

从该接口可以看到,实现的DataProvider封装了关于具体的连接对象,命令对象和参数类型的信息。

 

 

1.3实现DataProvider基础部分

 

SqlDataProvider类实现(基础部分)

private static System.Data.SqlClient.SqlConnection conn;

private System.Data.SqlClient.SqlCommand cmd;

 

/// <summary>

/// 默认构造函数

/// </summary>

public SqlDataProvider()

{

 

}

 

/// <summary>

/// 接受连接字符串

/// </summary>

/// <param name="connstr"></param>

public SqlDataProvider(string connstr)

    : this(connstr, "")

{

 

}

 

/// <summary>

/// 接受连接字符串和sql字符串

/// </summary>

/// <param name="connstr"></param>

/// <param name="sql"></param>

public SqlDataProvider(string connstr, string sql)

{

    conn = new System.Data.SqlClient.SqlConnection(connstr);

    cmd = new System.Data.SqlClient.SqlCommand();

    cmd.Connection = conn;

    cmd.CommandText = sql;

}

 

/// <summary>

/// 需要执行的SQL命令

/// </summary>

public string SQL

{

    set

    {

        cmd.CommandText = value;

    }

    get

    {

        return cmd.CommandText;

    }

}

 

/// <summary>

/// 当前的连接字符串

/// </summary>

public string ConnectionString

{

    get

    {

        return conn.ConnectionString;

    }

}

 

/// <summary>

/// 设置命令的类型

/// </summary>

public System.Data.CommandType CommandType

{

    set

    {

        cmd.CommandType = value;

    }

    get

    {

        return cmd.CommandType;

    }

}

 

 

从上述代码可以观察到,我们的DataProvider只向用户暴露了两个字符串数据:连接字符串和SQL指令字符串。而将ADO.Net特有的ConnectionCommand封装起来了。由于用户不需要了解到具体实例化的ConnectionCommand,则为以后对其他数据源提供者的扩展带来了机会。

 

 

1.4实现DataProvider数据执行部分

 

SqlDataProvider类实现(数据执行部分)

/// <summary>

/// 将SqlDataReader提交给具体的委托器处理

/// </summary>

/// <param name="readData"></param>

public void ExecuteReader(ReadData readData)

{

    using (conn)

    {

        conn.Open();

        System.Data.SqlClient.SqlDataReader dr = cmd.ExecuteReader();

        readData(dr);

        conn.Close();

    }

}

 

/// <summary>

/// 对连接执行 Transact-SQL 语句并返回受影响的行数

/// </summary>

/// <returns></returns>

public int ExecuteNonQuery()

{

    int result = -1;

    using (conn)

    {

        conn.Open();

        result = cmd.ExecuteNonQuery();

        conn.Close();

    }

    return result;

}

 

/// <summary>

/// 执行查询,并返回查询所返回的结果集中第一行的第一列。忽略其他列或行

/// </summary>

/// <returns></returns>

public object ExecuteScalar()

{

    object result = null;

    using (conn)

    {

        conn.Open();

        result = cmd.ExecuteScalar();

        conn.Close();

    }

    return result;

}

 

 

/// <summary>

/// 执行查询,并返回查询的DataSet

/// </summary>

/// <returns></returns>

public System.Data.DataSet ExecuteDataSet()

{

    System.Data.DataSet datadet = new System.Data.DataSet();

    using (conn)

    {

        System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();

        adapter.SelectCommand = cmd;

        conn.Open();

        adapter.Fill(datadet);

        conn.Close();

    }

    return datadet;

}

 

/// <summary>

/// 执行查询,并返回查询的Table

/// </summary>

/// <param name="tableIndex"></param>

/// <returns></returns>

public System.Data.DataTable ExecuteDataSet(int tableIndex)

{

    System.Data.DataSet datadet = ExecuteDataSet();

    if (datadet.Tables.Count > 0 && tableIndex < datadet.Tables.Count)

    {

        return datadet.Tables[tableIndex];

    }

    else

    {

        return null;

    }

}

 

/// <summary>

/// 执行查询,并返回查询的Table

/// </summary>

/// <returns></returns>

public System.Data.DataTable ExecuteDataTable()

{

    System.Data.DataTable table = new System.Data.DataTable();

    using (conn)

    {

        System.Data.SqlClient.SqlDataAdapter adapter = new System.Data.SqlClient.SqlDataAdapter();

        adapter.SelectCommand = cmd;

        conn.Open();

        adapter.Fill(table);

        conn.Close();

    }

    return table;

}

 

 

DataProvider提供ExecuteReaderExecuteNonQueryExecuteScalarExecuteDataSetExecuteDataTable方法,向使用者封装了两种不同的信息:

对执行数据访问的过程(Open后要Close等)已经在执行过程中的辅助对象(DataAdapter)信息。使用者仅需要简单的调用上述的方法,既可以得到他所关注的数据。

 

 

1.5实现DataProvider参数部分

 

SqlDataProvider类实现(参数部分)

/// <summary>

/// 添加一个Variant类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, object value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.Variant).Value = value;

}

 

/// <summary>

/// 添加一个Bit类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, bool value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.Bit).Value = value;

}

 

/// <summary>

/// 添加一个TinyInt类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, byte value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.TinyInt).Value = value;

}

 

/// <summary>

/// 添加一个SmallInt类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, short value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.SmallInt).Value = value;

}

 

/// <summary>

/// 添加一个Int类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, int value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.Int).Value = value;

}

 

/// <summary>

/// 添加一个BigInt类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, long value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.BigInt).Value = value;

}

 

 

/// <summary>

/// 添加一张图片

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, System.Drawing.Bitmap value)

{

    System.IO.MemoryStream ms = new System.IO.MemoryStream();

    value.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);

    AddParameters(parname, ms.ToArray(), ByteArrayFamily.Image);

}

 

 

/// <summary>

/// 添加一个Timestamp类型

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, byte[] value)

{

    AddParameters(parname, value, ByteArrayFamily.Timestamp);

}

 

/// <summary>

/// 添加一个字节数组族类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

/// <param name="dateType"></param>

public void AddParameters(string parname, byte[] value, ByteArrayFamily dataType)

{

    cmd.Parameters.Add(parname, DataTypeAdapter.ConvertSqlDbType(dataType)).Value = value;

}

 

 

 

/// <summary>

/// 添加一个字符类型数据,默认是NVarChar,长度是value.Length

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, string value)

{

    AddParameters(parname, value, StringFamily.NVarChar, value.Length);

}

 

/// <summary>

/// 添加一个字符族类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

/// <param name="length"></param>

public void AddParameters(string parname, string value, int size)

{

    AddParameters(parname, value, StringFamily.NVarChar, size);

}

 

 

 

/// <summary>

/// 添加一个字符族类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

/// <param name="dateType"></param>

/// <param name="length"></param>

public void AddParameters(string parname, string value, StringFamily dataType)

{

    AddParameters(parname, value,dataType, value.Length);

}

 

/// <summary>

/// 添加一个字符族类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

/// <param name="dateType"></param>

/// <param name="size"></param>

public void AddParameters(string parname, string value, StringFamily dataType, int size)

{

    cmd.Parameters.Add(parname, DataTypeAdapter.ConvertSqlDbType(dataType), size).Value = value;

}

 

 

/// <summary>

/// 添加一个SmallDateTime类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, DateTime value)

{

    AddParameters(parname, value, DateFamily.SmallDateTime);

}

 

/// <summary>

/// 添加一个日期族类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

/// <param name="dateType"></param>

public void AddParameters(string parname, DateTime value, DateFamily dataType)

{

    cmd.Parameters.Add(parname, DataTypeAdapter.ConvertSqlDbType(dataType)).Value = value;

}

 

/// <summary>

/// 添加一个Decimal类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, decimal value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.Decimal).Value = value;

}

 

/// <summary>

/// 添加Float类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, float value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.Float).Value = value;

}

 

/// <summary>

/// 添加一个UniqueIdentifier类型数据

/// </summary>

/// <param name="parname"></param>

/// <param name="value"></param>

public void AddParameters(string parname, System.Guid value)

{

    cmd.Parameters.Add(parname, System.Data.SqlDbType.UniqueIdentifier).Value = value;

}

 

 

ADO.Net对参数的处理冗长的很,需要很多代码,我们的DataProvider通过重载简单的实现了对参数的处理,使用者在大多数情况下只需要提供两个参数:参数的名称和值。DataProvider和根据值的类型推算应该使用具体的哪个System.Data.SqlDbType

 

1.6定义C#SQL数据类型的关系

 

不过,另人烦恼的是C#的数据类型和SQL的数据类型不是简单的一对一的关系,而是一对多的复杂关系,我们需要有一个方法来适配数据类型的不同。

 

数据类型的关系定义

/// <summary>

/// C#对于的SQL类型

/// </summary>

public enum StringFamily

{

    Char,

    NChar,

    NText,

    NVarChar,

    Text,

    VarChar   

}

 

/// <summary>

/// C#对于的SQL类型

/// </summary>

public enum DateFamily

{

    DateTime,

    SmallDateTime,

    Date,

    Time,

    DateTime2,

    DateTimeOffset   

}

 

/// <summary>

/// C#对于的SQL类型

/// </summary>

public enum ByteArrayFamily

{

    Binary,

    Image,

    Timestamp,

    VarBinary

}

 

 

1.7定义数据类型的适配器

 

 

DataTypeAdapter的定义

/// <summary>

/// SqlDbType数据类型和.NET Framework数据类型的适配器

/// </summary>

public static class DataTypeAdapter

{

    /// <summary>

    /// 将.NET Framework数据类型适配为SqlDbType数据类型

    /// </summary>

    /// <param name="data"></param>

    /// <returns></returns>

    public static System.Data.SqlDbType ConvertSqlDbType(StringFamily data)

    {

        switch (data)

        {

            case StringFamily.Char:

                return System.Data.SqlDbType.Char;

            case StringFamily.NChar:

                return System.Data.SqlDbType.NChar;

            case StringFamily.NText:

                return System.Data.SqlDbType.NText;

            case StringFamily.NVarChar:

                return System.Data.SqlDbType.NVarChar;

            case StringFamily.Text:

                return System.Data.SqlDbType.Text;

            default:

                return System.Data.SqlDbType.VarChar;

        }

    }

 

    /// <summary>

    /// 将.NET Framework数据类型适配为SqlDbType数据类型

    /// </summary>

    /// <param name="data"></param>

    /// <returns></returns>

    public static System.Data.SqlDbType ConvertSqlDbType(DateFamily data)

    {

        switch (data)

        {

            case DateFamily.Date:

                return System.Data.SqlDbType.Date;

            case DateFamily.DateTime:

                return System.Data.SqlDbType.DateTime;

            case DateFamily.DateTime2:

                return System.Data.SqlDbType.DateTime2;

            case DateFamily.DateTimeOffset:

                return System.Data.SqlDbType.DateTimeOffset;

            case DateFamily.SmallDateTime:

                return System.Data.SqlDbType.SmallDateTime;

            default:

                return System.Data.SqlDbType.Time;

        }

    }

 

    /// <summary>

    /// 将.NET Framework数据类型适配为SqlDbType数据类型

    /// </summary>

    /// <param name="data"></param>

    /// <returns></returns>

    public static System.Data.SqlDbType ConvertSqlDbType(ByteArrayFamily data)

    {

        switch (data)

        {

            case ByteArrayFamily.Binary:

                return System.Data.SqlDbType.Binary;

            case ByteArrayFamily.Image:

                return System.Data.SqlDbType.Image;

            case ByteArrayFamily.Timestamp:

                return System.Data.SqlDbType.Timestamp;

            default:

                return System.Data.SqlDbType.VarBinary;

        }

    }

 

}

 

 

通过上述的数据类型适配,我们将使用者和ADO.Net直接的具体关系弱耦合了。

 

 

1.8使用DataProvider

 

 

使用DataProviderSelect

DataProviders.IDataProvider provider = CreateDataProvider();

provider.SQL = "SELECT CompanyID as [Identity],Name,ShortName,Code,LegalEntity,Address,PostalCode,Type as CompanyType,CityID,Version " +

                "FROM lt_dictionary.Company WHERE CityID=@cityid";

provider.AddParameters("@cityid", cityID);

 

return provider.ExecuteDataTable();

 

 

 

使用DataProviderUpdate

DataProviders.IDataProvider provider = CreateDataProvider();

provider.SQL = "UPDATE lt_dictionary.Company " +

                "SET " +

                "Name=@name, " +

                "ShortName=@shortName," +

                "Code=@code," +

                "LegalEntity=@legalEntity," +

                "Address=@address," +

                "PostalCode=@postalCode, " +

                "Type=@type," +

                "CityID=@cityID " +

                "WHERE CompanyID=@id AND Version=@ver";

 

provider.AddParameters("@name", company.Name);

provider.AddParameters("@shortName", company.ShortName);

provider.AddParameters("@Code", company.Code);

provider.AddParameters("@LegalEntity", company.LegalEntity);

provider.AddParameters("@address", company.Address);

provider.AddParameters("@postalCode", company.PostalCode);

provider.AddParameters("@type", company.CompanyType.ToString());

provider.AddParameters("@cityID", company.City.Identity);

provider.AddParameters("@id", original_company.Identity);

provider.AddParameters("@ver", original_company.Version, DataProviders.ByteArrayFamily.Timestamp);

 

return provider.ExecuteNonQuery() > 0;

 

 

 

使用DataProviderInsert

DataProviders.IDataProvider provider = CreateDataProvider();

provider.SQL = "INSERT INTO lt_dictionary.City " +

                "([Name],PostalCode,DistanceCode,Province,Longitude,Latitude)" +

                "VALUES " +

                "(@Name,@PostalCode,@DistanceCode,@Province,@Longitude,@Latitude)";

provider.AddParameters("@name", city.Name);

provider.AddParameters("@postalCode", city.PostalCode);

provider.AddParameters("@distanceCode", city.DistanceCode);

provider.AddParameters("@province", city.Province);

provider.AddParameters("@longitude", city.Longitude);

provider.AddParameters("@latitude", city.Latitude);

 

return provider.ExecuteNonQuery() > 0;

 

 

通过上述的代码,可以发现,使用了我们的DataProvider后,程序员对ADO.Net的了解被降到最低程度,其只要关心具体的SQL指令和参数的赋值,其他内容不再需要其关注。很高程度的提高了程序员的开发效率。

 

 

二、打造实体基类

 

 关系型数据表中一般有共性的部分是所有的实体都有ID(但ID的类型不一样),很多业务表都有主从的关系。

 

 

2.1表定义

 

比如下面的表

 

City定义

CREATE TABLE [lt_dictionary].[City](

    [CityID] [int] IDENTITY(1,1) NOT NULL,

    [Name] [nvarchar](50) NOT NULL,

    [PostalCode] [dbo].[PostalCodeType] NOT NULL,

    [DistanceCode] [nvarchar](5) NOT NULL,

    [Province] [nvarchar](3) NOT NULL,

    [Longitude] [decimal](5, 2) NOT NULL,

    [Latitude] [decimal](5, 2) NOT NULL,

    [Enable] [dbo].[EnableType] NOT NULL CONSTRAINT [DF_City_Enable] DEFAULT ((1)),

    [LastEditDate] [dbo].[BusinessDateType] NOT NULL CONSTRAINT [DF_City_LastEditDate] DEFAULT (getdate()),

    [UpdateDay] AS (datediff(day,[LastEditDate],getdate())),

    [Version] [timestamp] NOT NULL,

 CONSTRAINT [PK_City] PRIMARY KEY CLUSTERED

(

    [CityID] ASC

)

 

 

这个城市表的IDint的。

 

BusinessOrders定义

CREATE TABLE [lt_business].[BusinessOrders](

    [BusinessOrderID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_BusinessOrders_BusinessOrderID] DEFAULT (newid()),

    [Number] [dbo].[BusinessOrderType] NOT NULL CONSTRAINT [DF_BusinessOrders_Number] DEFAULT ([dbo].[CreateBusinessOrderNumber]('Bz')),

    [Deadline] [dbo].[BusinessDateType] NOT NULL,

    [PaymentMethod] [nchar](2) NOT NULL,

    [PaymentEnterprise] [dbo].[DescriptionType] NOT NULL,

    [Origin] [dbo].[DescriptionType] NOT NULL,

    [Destination] [dbo].[DescriptionType] NOT NULL,

    [DeliveryType] [nchar](2) NOT NULL,

    [Level] [dbo].[LevelType] NOT NULL,

    [Remark] [dbo].[DescriptionType] NOT NULL,

    [Indicator] [nvarchar](3) NOT NULL,

    [FreightPayable] [dbo].[DescriptionType] NOT NULL,

    [WarehouseID] [int] NOT NULL,

    [OrderID] [uniqueidentifier] NOT NULL,

    [BusinessDate] [dbo].[BusinessDateType] NOT NULL CONSTRAINT [DF_BusinessOrders_BusinessDate] DEFAULT (getdate()),

    [StaffID] [int] NOT NULL,

    [Version] [timestamp] NOT NULL,

    [State] AS ([dbo].[GetBusinessOrderState]([BusinessOrderID])),

 CONSTRAINT [PK_BusinessOrders] PRIMARY KEY CLUSTERED

 

BusinessOrdersIDuniqueidentifier类型。

 

BusinessOrderDetaileds定义

CREATE TABLE [lt_business].[BusinessOrderDetaileds](

    [BusinessOrderDetailedID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_BusinessOrderDetaileds_BusinessOrderDetailedID] DEFAULT (newid()),

    [BusinessOrderID] [uniqueidentifier] NOT NULL,

    [Serial] [int] NOT NULL,

    [GoodsDescription] [dbo].[DescriptionType] NOT NULL,

    [Packing] [nvarchar](2) NOT NULL,

    [Quantity] [decimal](18, 2) NOT NULL,

    [TotalPackages] [decimal](18, 2) NOT NULL,

    [Weight] [decimal](18, 2) NOT NULL,

    [Measurement] [decimal](18, 2) NOT NULL,

    [Version] [timestamp] NOT NULL,

    [State] AS ([dbo].[GetBusinessOrderItmeState]([BusinessOrderDetailedID])),

    [CompleteQuantity] AS ([dbo].[GetBusinessOrderItmeCompleteQuantity]([BusinessOrderDetailedID])),

 CONSTRAINT [PK_BusinessOrderDetaileds] PRIMARY KEY CLUSTERED

 

BusinessOrderDetailedsIDuniqueidentifier类型,其外键对应的是BusinessOrders实体的ID

 

 

2.2关于实体基类定义的要求

 

我希望有这个的实体基类,该实体定义了所有的继承者(实体的具体实现类)都必须有ID属性,但ID属性的数据类型由各实体自己定义。我还希望,能在类的定义上看出有主从表的关系,并且能约束主从表的一些行为。而且我还希望基类能自动的实现对属性的赋值。

 

 

2.3定义EntityBase

 

EntityBase定义(继承部分)

[Serializable]

public abstract class EntityBase<T,ID> where T : EntityBase<T,ID>

{

 

    /// <summary>

    /// 所有的实体都必须有一个唯一标识,具体类型有实体各自实现

    /// </summary>

    [System.ComponentModel.DataObjectField(true, true, false)]

    public virtual ID Identity

    {

        set;

        get;

    }

}

 

 

EntityBase定义了一个ID的泛型,该泛型描述了继承者必须实现具体的ID类型。

 

 

2.4定义EntityBaseUndo功能

 

在没有泛型的年代时,基类无了解子类的类型,因此基类只能实现一些返回或参数是基本数据类型的方法,如果要为子类提供个性化的方法,基类只能以object对象返回,且要求子类实现数据类型的强制转换。但现在,EntityBase还提供了一个T类型,因此我们可以实现Undo的功能。

 

EntityBaseUndo部分)

/// <summary>

/// 实体是否支持撤销

/// </summary>

public abstract bool HasUndo

{

    get;

}

 

/// <summary>

/// 还可以撤销的次数

/// </summary>

public int UndoCount

{

    get

    {

        return undoStack.Count;

    }

}

 

/// <summary>

/// 得到实体的副本

/// </summary>

/// <returns></returns>

protected virtual T Clone()

{

    return (T)this.MemberwiseClone();

}

 

/// <summary>

/// 将复本入栈

/// </summary>

protected void Push()

{

    if (this.HasUndo)

    {

        this.Push((T)this.Clone());

    }

}

 

/// <summary>

/// 将复本入栈

/// </summary>

/// <param name="obj"></param>

private void Push(T obj)

{

    if (this.HasUndo)

    {

        undoStack.Push(obj.Clone());

    }

}

 

private System.Collections.Generic.Stack<T> undoStack = new Stack<T>();

 

/// <summary>

/// 将复本出栈

/// </summary>

/// <returns></returns>

private T Pop()

{

    if (undoStack.Count > 0)

    {

        return undoStack.Pop();

    }

    else

    {

        return null;

    }

}

/// <summary>

/// 撤销

/// </summary>

/// <returns></returns>

public T Undo()

{

    return Pop();

}

 

使用了泛型,我们在类的内部提供了泛型队列,然后返回值和参数值都是泛型T,该T将由各个子类来具体实现。

 

 

2.5定义EntityBase的数据访问能力

 

 

/// <summary>

/// 根据给定的连接字符串构造数据提供者

/// </summary>

/// <param name="connStr"></param>

/// <returns></returns>

protected static DataProviders.IDataProvider CreateDataProvider(string connStr)

{

    return new DataProviders.SqlDataProvider.SqlDataProvider(connStr);

}

 

 

 

2.6定义EntityBase的构造函数

 

EntityBase有一个接受System.Data.DataTable的构造函数,该构造函数将table中指定行的数据和本类的属性作对比,如果名称和数据类型匹配,则自动赋值。

 

 

EntityBase构造函数

/// <summary>

/// 按table的指定行数据进行属性的初始化

/// </summary>

/// <param name="table"></param>

/// <param name="indexRow"></param>

public EntityBase(System.Data.DataTable table, int indexRow)

{

    //遍历table中的每一列

    for (int i = 0; i <= table.Columns.Count - 1; i++)

    {

        //按列的名称,试图从当前对象中获取同名属性

 

        System.Reflection.PropertyInfo pinfo = this.GetType().GetProperty(table.Columns[i].ColumnName);

        if (pinfo != null)

        {//如果存在该属性

 

            object value = table.Rows[indexRow][table.Columns[i].ColumnName];//提取列的当前行值

 

            if (pinfo.PropertyType == table.Columns[i].DataType)//如果对象属性定义的类型和table的列的类型一致

            {

 

                pinfo.SetValue(this, value, null);//赋值

            }

            else

            {

                if (pinfo.PropertyType.IsEnum)//如果对象属性的值是枚举类型

                {

 

                    if (value.GetType() == typeof(int))//数据库中保存的是int类型,则直接为枚举赋值

                    {

                        pinfo.SetValue(this, value, null);//赋值

                    }

                    if (value.GetType() == typeof(string))//如果数据库中保存的是string类型

                    {

                        pinfo.SetValue(this, Enum.Parse(pinfo.PropertyType, value.ToString(), false), null);//赋值

                    }

                }

 

                //如果对象的属性是Bitmap类型,对应的数据值是byte[]

                if (pinfo.PropertyType==typeof(System.Drawing.Bitmap) && value.GetType()==typeof(byte[]))

                {

                    pinfo.SetValue(this, new System.Drawing.Bitmap(new System.IO.MemoryStream((byte[])value)), null);//赋值

                }

            }

        }

    }

}

 

 

2.7定义EntityBaseCreateInstance

 

虽然EntityBase的构造函数有能力实现对属性的自动赋值,但我们可能要实例对象的集合或决定table中是否有值,应此我们需要实现CreateInstance方法。

 

定义EntityBaseCreateInstances方法

/// <summary>

/// 通过table实例化一组对象

/// </summary>

/// <param name="table"></param>

/// <returns></returns>

public static List<T> CreateInstances(System.Data.DataTable table, int startRecord, int maxRecords)

{

    List<T> instances = new List<T>();

 

    for (int i = startRecord; i <= maxRecords; i++)

    {

        instances.Add(CreateInstance(table, i));

    }

    return instances;

}

 

/// <summary>

/// 通过table实例化一个对象

/// </summary>

/// <param name="table"></param>

/// <param name="startRecord"></param>

/// <param name="maxRecords"></param>

/// <returns></returns>

public static T CreateInstance(System.Data.DataTable table, int rowIndex)

{

    if (table.Rows.Count > rowIndex)

    {

        return (T)System.Activator.CreateInstance(typeof(T), table, rowIndex);

    }

    else

    {

        return null;

    }

}

 

/// <summary>

/// 默认按table的第一行实例化一个对象

/// </summary>

/// <param name="table"></param>

/// <returns></returns>

public static T CreateInstance(System.Data.DataTable table)

{

    return CreateInstance(table, 0);

}

 

 

/// <summary>

/// 通过table实例化一组对象

/// </summary>

/// <param name="table"></param>

/// <param name="startRecord"></param>

/// <returns></returns>

public static List<T> CreateInstances(System.Data.DataTable table, int startRecord)

{

    return CreateInstances(table, startRecord, table.Rows.Count - 1);

}

 

/// <summary>

/// 通过table实例化一组对象

/// </summary>

/// <param name="table"></param>

/// <returns></returns>

public static List<T> CreateInstances(System.Data.DataTable table)

{

    return CreateInstances(table, 0, table.Rows.Count - 1);

}

 

 

2.8使用EntityBase

 

 

 

返回单个实例对象

/// <summary>

/// 按指定的名字返回城市对象

/// </summary>

/// <param name="cityName"></param>

/// <returns></returns>

[System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select, false)]

public static City SelectByName(string cityName)

{

    DataProviders.IDataProvider provider = CreateDataProvider();

    provider.SQL = "SELECT CityID as [Identity],Name,PostalCode,DistanceCode,Province,Longitude,Latitude,Version " +

                    "FROM lt_dictionary.City WHERE Name=@name";

    provider.AddParameters("@name", cityName);

    return CreateInstance(provider.ExecuteDataTable());

}

 

 

返回集合对象

/// <summary>

/// 返回所有禁用状态的城市信息

/// </summary>

/// <returns></returns>

[System.ComponentModel.DataObjectMethod(System.ComponentModel.DataObjectMethodType.Select, false)]

public static List<City> SelectIsDisabled()

{

    DataProviders.IDataProvider provider = CreateDataProvider();

    provider.SQL = "SELECT CityID as [Identity],Name,PostalCode,DistanceCode,Province,Longitude,Latitude,Version " +

                    "FROM lt_dictionary.City WHERE Enable=0";

    //返回List<City>

    return CreateInstances(provider.ExecuteDataTable());

}

 

 

 

三、定义主从实体基类

 

定义主从实体基类的原因是我希望在类的定义时,可以很明确的了解类之间的主从关系。

 

 

 

3.1定义主表基类

 

定义PrimaryDataEntityBase

/// <summary>

/// 描述主从表的主表的数据实体

/// </summary>

/// <typeparam name="ID">主表实体的主键ID</typeparam>

/// <typeparam name="P">主表</typeparam>

/// <typeparam name="F">从表</typeparam>

public abstract class PrimaryDataEntityBase<ID, P, F> : EntityBase<P, ID>

    where P : PrimaryDataEntityBase<ID, P, F>

    where F : ForeignDataEntityBase<ID, P, F>

{

    /// <summary>

    /// PrimaryDataEntityBase的默认构造函数

    /// </summary>

    public PrimaryDataEntityBase()

    {

 

    }

 

    /// <summary>

    /// 按table的第一行数据进行属性的初始化

    /// </summary>

    /// <param name="table"></param>

    public PrimaryDataEntityBase(System.Data.DataTable table)

        : this(table, 0)

    {

 

    }

 

    /// <summary>

    /// 按table的指定行数据进行属性的初始化

    /// </summary>

    /// <param name="table"></param>

    /// <param name="indexRow"></param>

    public PrimaryDataEntityBase(System.Data.DataTable table, int indexRow)

        : base(table, indexRow)

    {

 

    }

 

    /// <summary>

    /// 装载当前从表的详细项

    /// </summary>

    protected abstract List<F> LoadDetailedItems();

 

    /// <summary>

    /// 存放外键表的数据项目的集合

    /// </summary>

    protected List<F> items = new List<F>();

 

    /// <summary>

    /// 获取外键表数据的集合

    /// </summary>

    public List<F> DetailedItems

    {

        get

        {

            return LoadDetailedItems();

        }

    }

 

 

    /// <summary>

    /// 返回外键表的数据项目数量

    /// </summary>

    public int DetailedItemCount

    {

        get

        {

            return items.Count;

        }

    }

 

    /// <summary>

    /// 将一个外键实体加入集合

    /// </summary>

    /// <param name="item"></param>

    /// <returns></returns>

    public abstract void Add(F item);

 

    /// <summary>

    /// 从集合中移除一个外键实体

    /// </summary>

    /// <param name="item"></param>

    public abstract void Remove(F item);

 

    /// <summary>

    /// 从集合中移除一个外键实体

    /// </summary>

    /// <param name="index"></param>

    public abstract void RemoveAt(int index);

 

    /// <summary>

    /// 返回或设置匹配索引的订单详细项

    /// </summary>

    /// <param name="index"></param>

    /// <returns></returns>

    public abstract F this[int index]

    {

        set;

        get;

    }

}

 

 

 

3.2定义从表基类

 

定义ForeignDataEntityBase

/// <summary>

/// 描述主从表的从表的数据实体

/// </summary>

/// <typeparam name="ID">从表实体的主键ID</typeparam>

/// <typeparam name="P">主表</typeparam>

/// <typeparam name="F">从表</typeparam>

public abstract class ForeignDataEntityBase<ID, P, F> : EntityBase<F, ID>

    where P : PrimaryDataEntityBase<ID, P, F>

    where F : ForeignDataEntityBase<ID, P, F>

{

    /// <summary>

    /// ForeignDataEntityBase的默认构造函数

    /// </summary>

    public ForeignDataEntityBase()

    {

 

    }

 

    /// <summary>

    /// 按table的第一行数据进行属性的初始化

    /// </summary>

    /// <param name="table"></param>

    public ForeignDataEntityBase(System.Data.DataTable table)

        : this(table, 0)

    {

 

    }

 

    /// <summary>

    /// 按table的指定行数据进行属性的初始化

    /// </summary>

    /// <param name="table"></param>

    /// <param name="indexRow"></param>

    public ForeignDataEntityBase(System.Data.DataTable table, int indexRow)

        : base(table, indexRow)

    {

 

    }

 

 

 

    /// <summary>

    /// 对应主键实体

    /// </summary>

    [System.ComponentModel.DataObjectField(false, false, false)]

    public P RelationOrder

    {

        set;

        get;

    }

}

 

 

3.3使用主从表基类

 

 

 

/// <summary>

/// 客户委托单

/// </summary>

[System.ComponentModel.DataObject(true)]

public class BusinessOrder : LogisticsOrderBase<BusinessOrder, BusinessOrderItem>

{

}

 

 

/// <summary>

/// 委托单详细

/// </summary>

[System.ComponentModel.DataObject(true)]

public class BusinessOrderItem : DetailedItemBase<BusinessOrder, BusinessOrderItem>

{

}

 

 

 

现在我们的类在定义的时候,就可以非常明确的描述了主从实体的关系,并拥有了数据自动属性装载的能力。

 

 

 

 

四、结论

 

反射可以让我们动态的了解对象的所有成员,通过对tabel的遍历和对本对象的遍历,我们可以很方便的实现对属性的赋值,减少了程序员的重复性工作。而减少代码并不是可以偷懒,而是可以降低错误的产生机会。

而泛型可以在具体的实现之前,定义将来的类型,并且能对类型做出很多约束。使用了泛型以后,我们可以在基类为子类的多态做出更多的多态实现,现在我们的类对于静态方法也可以产生多态了,不是吗?

 

 

 

 

 

posted on 2008-11-19 16:03  害羞的狮子王  阅读(1548)  评论(6编辑  收藏  举报