zoukankan      html  css  js  c++  java
  • 使用泛型和反射,打造我们的完美实体基类(全文)

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

    背景

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

    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的遍历和对本对象的遍历,我们可以很方便的实现对属性的赋值,减少了程序员的重复性工作。而减少代码并不是可以偷懒,而是可以降低错误的产生机会。

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

  • 相关阅读:
    Docker-compose编排微服务顺序启动解决方案
    在笔记本上使用virtualbox搭建lvs dr 实验遇到的问题
    MongoDB安装配置(RedHat/CentOS)
    wget 用法
    Ubuntu查看crontab运行日志
    CentOS-6.5安装Zabbix 3.0.4
    centos 6 安装vsftpd与PAM虚拟用户
    python socket常用接口说明
    cmake 构建工程
    std::vector的下标访问和迭代器访问的效率
  • 原文地址:https://www.cnblogs.com/shyleoking/p/1336756.html
Copyright © 2011-2022 走看看