实体类的变形【2】—— 行列转换

     上次说了一下在网页里面显示列表数据的情况,这个应用范围太小了,添加、修改怎么办呢?网站的后台管理、OA、CRM等怎么办?还是这样处理显然是不行的。

 

我们还是看一个小例子,这回是数据库设计的。

 

假设我们要做一个小学的成绩单,设计一个成绩表

小学生成绩表

字段:学生名称、语文成绩、数学成绩、美术成绩等。

小学里的课程是有限的,就那么几个,都作为字段放在表里面就ok了。

如果我们现在要做一个中学的成绩单呢?物理、化学、生物、地理、历史课程增加了不少,还是往用往表里面增加字段的方式吗?好像也勉强可以。

如果我们现在要做一个大学的成绩单呢?还用这种方法就绝对不行了。换一个方法吧,行列转换一下。

大学成绩单

字段:学生ID、课程ID、成绩

这样三个字段搞定,当然还可以再根据情况增加一个字段,比如系ID、专业ID等,没有真正做过,只是猜想。这样学生和课程就可以随意组合了。当然他有一个很大的缺点,就是显示成绩单的时候有点麻烦,但是这个不在本次讨论的范围内。

 

 

     好了,回到程序的世界里面。上次我们定义了一个类用来显示列表数据,

 

public class Group_topic_Show
 {
  public string Topic;  //话题名称
  public string TopicURL;  //话题的连接地址
  public string Group;  //话题所属小组的名称,来自“小组”表
  public string GroupURL;  //小组的连接地址
  public string View;   //回应/浏览
  public string SendDate;  //话题发表日期
  public string Author;  //作者姓名,也是来自另一个表
  public string AuthorURL; //作者的连接地址
   
 }

 

 

     假设我们要往这个表里面添加数据怎么办呢?

     等等这个类是用于显示数据的,添加的时候还可以使用这个类吗?好像不行了吧,要换成下面的形式

 

public class Group_topic
 {
  public string Topic;  //话题名称
  public string TopicURL;  //话题的连接地址
  public string GroupID;  //话题所属小组的ID
  public string View;   //回应/浏览,这个可以不填(利用数据库的默认值),或者在程序里填0。
  public string SendDate;  //话题发表日期,这个一般也是取默认值
  public string AuthorID;  //作者姓名ID
  public string Content;  //主题内容
  //其他字段
 }

     

     这就是第一个问题:数据库里有三个表(主题、分组、会员),发表主题的时候往一个表里面写数据就可以了,但是显示主题的时候还需要两外两个表里面的数据,那么这时候实体类如何定义呢?我这里想到了两种方案:

 

     第一种方案:一个表只对应一个实体类,三个表就会有三个实体类,这样添加的时候没有什么问题,显示的时候就要用类的关系的方式联系到一起,具体怎么做我还不知道呢。
     这样做有一个明显的缺点,就是在显示列表的时候,主题的内容也会被加载进来,但是在列表的时候根本就不需要,由于这个字段里面的数据量比较大,所以就显得很浪费资源!

 

     第二种方案:添加的时候用一个实体类 Group_topic,显示的时候用另一个实体类 Group_topic_Show,就像上面定义的两个实体类。但是这个也有很明显的缺点,好多的字段名重复出现!如果这时候修改了字段名的话,修改量就会增倍!

     这个就是我不想用三层的一个原因,总是要想实体类和数据表如何对应,很烦,左也不是,右也不是,不知道如何来做,那就干脆不用三层的这种形式吧。看了亚同学的帖子,好像他也有这样的问题?!我还以为只有我一个人有呢。

     好了先不说这个问题了,我们继续。假设我们定义了一个 Group_topic类,要用这个类来实现添加、修改数据库。显示的问题先不考虑。

 

一般的步骤:

1、UI里面放置控件
2、取值,给实体类赋值
3、验证,逻辑处理
4、拼接SQL语句,或者设置存储过程的参数
5、提交给数据库

  

我见过的一种方式是这样的,数据层里写这样的代码

sql = "insert into Group_topic (Topic,GroupID,Author) values (@Topic,@GroupID,@Author)";

//添加存储过程的参数。
cmd.Parameters.Add("@GroupID", SqlDbType.Int).Value = fam.GroupID;  //自动ID 1:为系统管理应用

这样写,我觉得挺繁琐的,为什么说繁琐呢?我们看看字段名出现了几次?(Topic,GroupID,Author)这里出现了一次,(@Topic,@GroupID,@Author)" 这里是第二次,("@GroupID", 又是一次,fam.GroupID 这是第四次了!

 

就是一个字段名呀,出现了四次,烦不烦呀,修改的时候,只是数据层就有四个地方,恐怖!虽然你可以使用代码生成器搞定,但是当需求有变化的时候呢,代码生成器可以很好的解决吗?

 

“嘻嘻哈哈”极力推荐我看一个ASP.NET权限管理系统的源代码,三层写的,上面的给参数赋值的语句就是从“权限管理系统”copy出来的。看了之后有几点心得:

 

1、他的代码真的像我上面写的过程,基本差不多。
2、我才知道为什么代码生成器会这么火,因为没有代码生成器的帮助,根本就没有办法写三层的代码。
3、抽象在哪里?相似的函数太多了。

 

Code
public override int sys_ApplicationsInsertUpdate(sys_ApplicationsTable fam)
        {
            int rInt = 0;
            using (SqlConnection Conn = GetSqlConnection())
            {
                SqlCommand cmd = new SqlCommand("sys_ApplicationsInsertUpdateDelete", Conn);
                cmd.CommandType = CommandType.StoredProcedure;
                //设置参数
                cmd.Parameters.Add("@DB_Option_Action_", SqlDbType.NVarChar).Value = fam.DB_Option_Action_; //操作方法 Insert:增加 Update:修改 Delete:删除 Disp:显示单笔记录
                cmd.Parameters.Add("@ApplicationID", SqlDbType.Int).Value = fam.ApplicationID;  //自动ID 1:为系统管理应用
                cmd.Parameters.Add("@A_AppName", SqlDbType.NVarChar).Value = fam.A_AppName;  //应用名称
                cmd.Parameters.Add("@A_AppDescription", SqlDbType.NVarChar).Value = fam.A_AppDescription;  //应用介绍 
                cmd.Parameters.Add("@A_AppUrl", SqlDbType.VarChar).Value = fam.A_AppUrl;  //应用Url地址
                Conn.Open();
                rInt = Convert.ToInt32(cmd.ExecuteScalar());
                cmd.Dispose();
                Conn.Dispose();
                Conn.Close();
            }
            return rInt;
        }

        /// <summary>
        /// 返回sys_ApplicationsTable实体类的ArrayList对象
        /// </summary>
        /// <param name="qp">查询类</param>
        /// <param name="RecordCount">返回记录总数</param>
        /// <returns>sys_ApplicationsTable实体类的ArrayList对象</returns>
        public override ArrayList sys_ApplicationsList(QueryParam qp, out int RecordCount)
        {
            PopulateDelegate mypd = new PopulateDelegate(base.Populatesys_Applications);
            return this.GetObjectList(mypd, qp, out RecordCount);
        }

 

     像这样形式的函数出现了好多好多,估计项目越大,这样的函数就会越多!而且为了支持多种数据库,他写了三个文件SqlDataProvider.cs、OracleDataProvider.cs、AccessDataProvider.cs,这三个文件里的代码也都差不多。为什么要一边一边的写类似的函数呢?就是因为实体类的属性是不一样的!

 

     有一点很奇怪,SQL SERVER使用存储过程,而ACCESS确实用参数化的SQL语句,为什么不都是用参数化的SQL语句呢?难道在SQL SERVER的存储过程里面还要做一些判断吗?

 

     相似的代码重复出现,“重用”在哪里呢?!

     为了解决这样的问题,有些同学提出来了使用反射,估计是使用反射地方法把属性名称反射成字段名称,再拼接参数化的SQL语句,然后再添加存储过程的参数。(不知道参数类型是如何得到的?)
     这样倒是可行,但是效率上有一点点损耗,从原理上来说也是挺“郁闷”的,我们在编码的时候用字符换的形式定义了实体类的属性,然后编译,变成了一种形式,然后用的时候在通过反射,再把这种形式变回字符串的形式,绕了一圈,为什么绕圈圈呢?

     所以我给实体类变一下形式,“行列转换”了一下。

 

class ColInfo()
{
 public string ColName;
 public string ColValue;
}

 

上面的属性就变成了这样

ColInfo tmpColInfo = new ColInfo();
tmpColInfo.ColName = "Topic";
tmpColInfo.ColValue ; //这个在定义的时候是没法赋值的,需要在UI里面赋值。

 

     然后再放在一个集合里面,一个有属性(字段)组合而成的“类”(集合)就诞生了。这样我们就做到了以属性(字段)为最小单位进行随意组合,也可保证字段名在程序里面只出现一次,这样就大大减少了修改的机会。

保存数据的时候,代码也可以进一步的抽象,抽象成一个函数来处理。不用一个类写一套函数了,

     只有两个属性还不够,字段类型怎么办呢?我们在扩充一下吧,加几个属性,

/// <summary>
    /// 字段的基本信息的描述
    /// </summary>
    public class ColumnsInfoBase        
    {
        #region 字段的基本信息的描述
        /// <summary>
        /// 配置信息里面的字段的标识
        /// </summary>
        public int ColumnID = 0;

        /// <summary>
        /// 数据库里的字段名称
        /// </summary>
        public string ColSysName = "";

        /// <summary>
        /// 显示给客户看的名称
        /// </summary>
        public string ColName = "";

        /// <summary>
        /// 字段类型,int nvarchar 等
        /// </summary>
        public string ColType = "";

        /// <summary>
        /// 字段大小
        /// </summary>
        public Int32 ColSize = 0;

        /// <summary>
        /// 字段值
        /// </summary>
        public string ColValue;

        
        /// <summary>
        /// 查询方式
        /// </summary>
        public Int32  FindKind = 0;
        #endregion

    }

     

     这样呢,我们就可以根据这些信息来拼接参数化的SQL语句和存储过程的参数了。

对了,集合的方式还没有写呢,

 

Code
Dictionary<int, ColumnsInfoForm>  dic_BaseCols = new Dictionary<int, ColumnsInfoForm>();
ColumnsInfoForm info;

            foreach (DataRow dr in dt.Rows)
            {
                info = new ColumnsInfoForm();

                info.ColumnID = Int32.Parse(dr["ColumnID"].ToString());
                info.ColSysName = dr["ColSysName"].ToString();
                info.ColName = dr["ColName"].ToString();
                info.ColType = dr["ColType"].ToString();
                info.ColSize = (Int32)dr["ColSize"];
                info.FindKind = (Int32)dr["FindKind"];

                dic_BaseCols.Add(info.ColumnID, info);

            }

     为什么 key 不用字段名而用字段ID呢?因为字段名是“不可靠”的,字段名是会变的呀,变了怎么办呀?就会有修改代码的可能。当然也不是说字段ID是绝对不会变化的,字段ID只会被“删除”,而不会被修改,字段ID要比字段名稳定很多,因为字段ID是与业务逻辑一点关系都没有的,而字段名多少和业务逻辑是有关联的。

     不过这样“实体类”就由装载数据变成了对字段的描述,有了这些信息,我们就可以用作拼接SQL语句(参数化的或者非参数化的),设置存储过程的参数,加上查询方式,就可以拼接“查询条件”,就是SQL语句的Where后面的部分。

一个函数就可以搞定了,不用写这么多的类似的函数了!我们可以继续进行扩展,可以描述字段在UI里的表现形式,比如用什么控件(文本框、下拉列表框还是复选框等),验证方式等。还有就是表单的布局。可以描述列表的表现形式,那个字段在前面,那个在后面,是否需要格式化(Format)等。

 

     其实这个就是我的表单控件、查询控件、显示数据的控件里面使用的一种载体。一开始写的时候还没有意识到,写完了之后才发现,自己居然写成了这种形式。当然在我的控件里面,类的加载(实例化)都是依据配置信息来做的。

优点:
1、以字段为最小单位。这样数据表里面的字段,在“代码”里面只会出现一次,然后可以让他们灵活的组合,不管是那个表里的字段都可以组合在一起,放在一个集合里面。

2、需要字段名的时候,使用 属性就可以了,不用反射了。

 

 

     第一个优点是相对于现在三层里面的实体类来说的,三层里的实体类都是以表为最小单位的,属性只能是类里面的一部分,不能独立存在,这样就很不灵活,这就是第一个问题的由来。

 

时间: 2024-10-26 11:47:48

实体类的变形【2】—— 行列转换的相关文章

实体类的变形【1】—— 餐盘原理

      在亚历山大同学的post里面我说可以让实体类和表不必一一对应,但是并没有详细说明如何来做,也有人想问我是怎么做的,那么我就说一下.先说一个简单一点的,那就是在网页里面显示列表数据的情况,其他的下次再说.我们先来看一个生活中的情况,然后再说程序里面如何来做.   餐盘原理--模糊对应        餐盘,大家去食堂吃饭的时候,是不是会用一个长方形的餐盘来盛饭和菜呢?长方形的餐盘里有一个大一点的长方形的阁子,可以用来盛饭,当然也可以放馒头.花卷.面条等:有两.到四个个小一点的阁子,可以盛

简单实体类和xml文件的相互转换方法

最近写一个题目,要求将一组员工实体类转换成xml文件,或将xml文件转换成一组实体类.题目不难,但写完感觉可以利用泛型和反射将任意一个实体类和xml文件进行转换.于是今天下午立马动手 试了下,做了个简单的模型,可以将简单的实体类和xml文件进行相互转换,但对实体类的属性类型有限制,目前只支持String, Integer, Double三种类型.但是后面可以扩展. 我的大概思路是这样的,只要能拿到实体类的类型信息,我就能拿到实体类的全部字段名称和类型,拼属性的set和get方法更是简单明了,这时

【实体类变形】—— 元数据(另类ORM) 描述字段的数据

       放假了,不知道有没有加班的,先祝大家国庆节快乐!      上次说得有点乱,"行列转换"这个词可能误导了大家,那么把这个词扔掉吧.我们重新开始.假设我们有一个News表,我们要往里面添加数据,我们先只考虑保存数据的部分. 一.我们定义一个类.变形的"实体类"  public class ColumnsInfoBase            {         #region 字段的基本信息的描述         /// <summary>

ASP.NET JSON字符串与实体类的互转换的示例代码

 本篇文章主要是对ASP.NET JSON字符串与实体类的互转换的示例代码进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助 还是先封装一个类吧! 这个类网上都可以找到的!有个这个类,一切都将变得简单了,哈哈. 代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Runtime.Serialization.Json; using Sys

dalvik-在做xml与java实体类的转换时,导入的jar包都会让eclipse报错

问题描述 在做xml与java实体类的转换时,导入的jar包都会让eclipse报错 我开始用XStream,后来又根据网上的代码导入javax.xml.bind包来进行解析,但是这两种都会报错:Conversion to Dalvik format failed with error 1,我百了一下,也不是很明白,还请大家解答,先谢谢各位了.. 解决方案 可以试下这个方案http://dev.wo.com.cn/docportal/doc_queryMdocDetail.action?mdoc

ASP.NET自带对象JSON字符串与实体类的转换_实用技巧

关于JSON的更多介绍,请各位自行google了解!如果要我写的话,我也是去Google后copy!嘿嘿,一直以来很想学习json,大量的找资料和写demo,总算有点了解! 切入正题! 还是先封装一个类吧! 这个类网上都可以找到的!有个这个类,一切都将变得简单了,哈哈. using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Runtime.Serializ

ASP.NET JSON字符串与实体类的互转换示例代码_实用技巧

还是先封装一个类吧! 这个类网上都可以找到的!有个这个类,一切都将变得简单了,哈哈. 复制代码 代码如下: using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Runtime.Serialization.Json;using System.ServiceModel.Web;///记得引用这个命名空间using System.IO;using System.Tex

行列转换-sql 检索结果拼接成字符串。。

问题描述 sql 检索结果拼接成字符串.. 因项目中业务要求所致,写SQL的时候对检索结果的最后两列进行了行列转换.如下pivot (sum(sl) for community_code in ('0201' 社区1 '0202' 社区2'0203' 社区3))因为社区的个数不确定,就导致了输出的结果集中列的个数也不确定.所以用实体类进行接收结果集的时候就遇到了麻烦.对应上述问题,决定将结果集的每一行拼接成一个字符串.那么问题就来了,输出的数据和社区名称对应不起来了..不知道有没有什么好的方法解

C# 三层中的实体类有什么用呢?

问题描述 我想问问假设现在有实体类(Model.cs)privatestringUID=string.empty;privatestringUNAME=string.empty;publicstringID{get{returnUID;}set{UID=value;}}publicstringNAME{get{returnUNAME;}set{UNAME=value;}}然后到业务逻辑层(BLL)最后就数据链路层(DAL)当中实体类就是用来干什么的,能举个例子说明吗? 解决方案 解决方案二:这样