一、 SQLServer主要功能:
1、 这是Microsoft SQL Server特定的PetShop DAL层实现,执行了IDAL接口定义的各方法
二、 实现细节:
1、 SQLHelper.cs文件:就是MS DAAB
2、 主要定义了以下几个方法对数据库操作:
3、 ExecuteNonQuery()方法用于根据用户的要求(SQL语句)对数据库执行操作,这包括3个重载方法
4、 注意:ExecuteNonQuery()方法接受的SQL语句的参数,是一SqlParameter[]数组的形式出现的!!!这样的设计方法,避免了由于不同SQL语句中的参数个数不同,而为每一个不同的SQL语句定义一个ExecuteNonQuery()方法的问题!!!而改由“SQL语句模板”和“SqlParameter[]变量数组”自动进行变量对应!!!!!!
5、 ExecuteReader()方法根据用户的要求(SQL语句)对数据库执行查询,并返回给用户一个包含特定查询结果的SqlDataReader,供调用程序(用户)使用
6、 ExecuteScalar()方法,用于根据用户的要求(SQL语句)对数据库执行查询,并返回查询所返回的结果集中第一行的第一列,而忽略额外的列或行,这包括两个重载方法
7、 CacheParameters()方法,用于将SqlParameter[]数组存储在Hashtable中,并用一个“键”进行标识
8、 GetCachedParameters()方法,用于将CacheParameters()方法存储在Hashtable中的SqlParameter[]数组,根据设置好的“键”进行提取
9、 Account.cs文件:实现了IDAL中定义的方法
10、 以SignIn()方法为例,SignIn()方法的功能是解决用户账户登录的整个问题,该方法中包含两部分,一部分是逻辑判断和操作,还有一部分是与数据库交互。逻辑判断部分一般以数据库交互的结果为依据,给调用程序返回合适的结果
11、 变量添加到SqlParameter[]数组的顺序一定要和在SQL模板中定义的变量的顺序一致!!!
12、 以SignIn()方法为例,演示SQLServer DAL实现在IDAL中定义的某一个方法过程:
(1) BLL层调用IDAL接口层的Account类的SignIn()方法
(2) IDAL接口实际是使用DAL层的Account类的SignIn()方法
(3) 由于Account类就是针对Account数据库表进行操作的,所以在Account类当中,对Account数据库表进行增删改查的SQL语句都已经写成了模板
(4) 在用户(BLL层)调用SignIn()方法的时候,肯定提供了相应的变量,如用户名UserId和密码Password,这些都对应于SQL语句模板当中需要的变量
(5) 为了满足使用“SQLHelper类的方法”的格式,所以要把用户传入的变量(UserId和Password),也就是SQL语句模板需要的变量,都包装在一个SqlParameter[]数组当中,将所有参数作为一个数组传递给SQLHelper类的相应方法,进行操作
(6) 返回一个新SqlParameter[]数组的方法也有讲究!!!这里在SignIn()方法中又调用了一个GetSignOnParameters()方法!!!
(7) 这个GetSignOnParameters()方法首先判断,如果在SQLHelper.cs的Hashtable中存在(缓存了)一个“空的SqlParameter[]变量数组(也相当于模板)”,则直接将此“空的SqlParameter[]变量数组(模板)”返回给调用程序(也就是SignIn()方法);相反,若不存在这样一个“空的SqlParameter[]变量数组(模板)”,则根据向SignOn表中插入数据的SQL语句模板(对变量的需求),创建一个“空的SqlParameter[]变量数组(模板)”,并缓存在SQLHelper的Hashtable当中,然后返回给调用程序。
(8) 这里有一个重要的内容!!!Hashtable parmCache究竟缓存的是什么内容!!!实际上,parmCache缓存的是各“SQL语句模板”对应(使用)的“空白SqlParameter[]变量数组(模板)”!!!这个数组不是针对某个实例的(这里的某个实例指的是某一套用户名和密码,如用户名为A,密码为B),而是空白的、通用的、针对某个“SQL语句模板”的“SqlParameter[]变量数组(模板)”!!!!!!
(9) PetShop在这里使用缓存参数数组模板的方式,是为了提高性能,不必每次都为一个用户的登录(每套用户名和密码实例)而建立一个新的“SqlParameter[]变量数组”
(10) 这里终于理解了为什么GetCacheParameter()方法,就算查找到有对应的“SqlParameter[]变量数组(模板)”也不直接返回,而是Clone一个之后再返回!!!完全是因为在Hashtable中,我们需要存储的只是一个“SqlParameter[]变量数组(模板)”,而不是一套用户名和密码的实例!!!如果直接返回存储在Hashtable中的“SqlParameter[]变量数组”,则在不同的用户登录后,都会改写模板的内容,相当于将模板实例化了,而这是错误的!!!
(11) 当SignIn()方法得到了一个空白的“SqlParameter[]变量数组”时,就根据调用SignIn()方法时提供的具体用户名和密码,初始化这个“SqlParameter[]变量数组”。而这个“SqlParameter[]变量数组”正是SQLHelper的ExecuteReader()方法需要的参数(类型)之一!!!
(12) 根据用户登录动作时提供的用户名和密码信息,在数据库中去找对应的条目(默认用户的输入总是正确的)。如果找到了,则认为用户登录是成功的,并且直接读取用户的其他个人信息,实例化成为一个AccountInfo(在Model中定义)类型的对象,返回给BLL调用程序!!!!!!
13、 GetAddress()方法的实现方式与SignIn()方法相同
14、 Insert()方法:用于向数据库中插入一条新的用户账户信息
15、 由于需要将一个用户注册的信息分别写入3个数据库表当中,为避免写入的过程中发生问题,这里使用一个事务,将3个写入操作打成一个“包”——要么全部执行成功,要么一条都不执行!
16、 注意Transaction实例与conn对象的联系——trans=conn.BeginTransaction();
17、 Inventory.cs文件:对某个Item的库存数量进行查询和更改,其中更改主要指根据订单的发货量进行减少
18、 CurrentQtyInStock()方法没有使用Hashtable缓存SQL语句的变量
19、 TakeStock()方法,传入的参数是一个LineItemInfo[]数组,其中包含了某张订单中用户购买的所有物品。既然包括了不同的物品(Item),也就相应地有不同的ItemId,每个Item也有对应的购买数量Quantity
20、 实际上,需要为每一个Item(这里的Item指的是LineItemInfo[]数组中的某一个元素!)数量的减少操作定义一个SQL语句,而这里采用的是将对此张订单中每一项需要购买的Item的数量的减少操作定义一个SQL语句,再将“根据所有要购买Item定义的SQL语句”“串联”起来,中间用“分号”分隔,然后拿到数据库中去执行!!!
21、 Item.cs文件:主要完成对Item的两个操作
22、 GetItemsByProduct()方法,根据传入的productId,将此Product类别下的所有Item都选出来。注意,返回的结果是一个IList接口的形式,可以根据此接口,使用index值枚举ArrayList中的每个Item
23、 GetItem()方法很简单,就是根据传入的itemid值,返回给调用程序相应的ItemInfo对象
24、 在Item.cs文件的两个方法中,没有用到Hashtable缓存
25、 Order.cs文件:
26、 第一个函数Insert()很特殊,值得研究一下代码实现方式。Insert()方法实现的是向数据库中增加一条订单(Order)记录,而增加一条订单记录需要向三个表当中写入数据,一个是Orders表,它存储了这个订单的所有信息;第二个是LineItem表,其中存储了此订单中,用户订购的所有物品(Item)及每种Item的数量;第三个是OrderStatus表,它存储了订单的状态
27、 Insert()方法的整体思路也是逐步构建SQL语句,并采用了与Inventory.cs文件中TakeStock()方法相同的实现机制,将“向LineItem表插入某张订单中用户订购的所有物品”的SQL语句,及其所需变量构造出来。这里的SQL执行过程(插入)与TakeStock()方法(更新)类似
28、 在Insert()方法执行数据库操作时,还提供了“检错”机制。所执行的SQL语句经过特殊的处理(定义了返回@ID和@ERR),返回参数两个值,第一个是新插入Orders表的订单的订单ID号,第二个就是在SQL语句执行过程中的错误数量。如果出现错误,则抛出异常!
29、 第二个GetOrder()方法比较简单,主要是根据传入的Order号,返回一个OrderInfo对象
30、 Product.cs文件:实现两个功能,一是根据给定的Category,将此Category下所有的Product返回,形式为一个IList接口(ArrayList对象的引用?);第二个是一个具备“搜索”功能的方法,根据传入的关键字数组,返回符合条件的Product ArrayList 接口IList
31、 GetProductsBySearch()方法,根据用户提供的输入字符串(数组),返回给调用程序一个满足要求的ArrayList。
32、 注意:GetProductsBySearch()方法使用了一个小技巧,拼接了包含查询条件的SQL语句。在自己的项目中可以参考使用
33、 Profile.cs文件:根据最感兴趣的Category的内容,返回一个BannerPath字符串
三、 启发:
1、 在IDAL层和DAL层中定义的所有方法,都是为了支持“BLL层的需要”的!!!都是为了实现“BLL层的逻辑对数据库进行操作的需要”而建立的!!!
2、 “一个数据库表” 对应 “一个Model” 对应 “一个IDAL” 对应 “一个SQLServerDAL”
3、 软件开发过程的问题:是否应先进行数据库建模,然后?有网友说是先规划Model,然后设计数据库
4、 由于对特定数据库表进行操作的(增、删、改、查)SQL语句基本格式都是固定的,而针对不同的条目(情况),只有个别的参数不同。所以在针对每个数据库表编制的DAL层的类当中(如Account数据库表对应的Account类),编写好了针对当前数据库表进行增删改查的SQL语句(模板)。模版当中预留了变量的位置,用于处理不同的操作实例的条目,适应所有的情况。当然,对当前数据库表没有涉及到的操作,可以不进行定义!SQL语句模板也要包括所有特定功能的增删改查操作!!!
5、 捋清了从“BLL层调用——具体数据库操作”的整个设计和实现过程
6、 强调注意:在DAL层也是要分层的!!!直接操作ADO.NET组件,对数据库进行增删改查的SQLHelper类(DAAB),又单独被抽象出一小层,这样解决了High Level 接口的问题!!!以上知识可以参考MSDN WebCast Modern C#的第8讲
7、 由于第一遍看PetShop,所以重点看的是系统结构和设计思路。实现具体功能的代码部分也做了简单的研究,但是对于“为什么使用这种方法实现”等问题并没有搞清楚!这要等到自己动手做Demo系统的时候再仔细地分析
四、 问题:
1、 private static Hashtable parmCache = Hashtable.Synchronized(new Hashtable());与直接private static Hashtable parmCache = new Hashtable());有何区别?也就是说对Hashtable进行同步的意义是什么?
2、 SQLHelper.cs中的Hashtable parmCache的缓存范围是什么?
3、 为什么只有Account类中的SQL语句模板的SqlParameter[]变量数组要缓存在Hashtable中,而Inventory类的却不缓存呢?
4、 多条SQL语句,中间使用“分号”分隔,然后一起向数据库执行,是否能成功?(为此,准备在本机数据库中做试验!!!结果:可以执行成功!!!)
5、 Hashtable的问题:在TakeStock()方法中,SQLHelper类的Hashtable的作用是什么?由于没有使用SQLHelper类对数据库进行操作,而直接实例化了SqlCommand对象,所以就不涉及到“必须使用SqlParameter[]数组”的问题!
6、 还有,TakeStock()方法在执行数据库更新的时候,为什么不使用SQLHelper方法了?这完全可以应用啊!!!