Ado.net继续学习_人力管理系统开发

一、相关的WPF基础

  1. 定义类,定义属性
  2. new一个实例, 给要绑定的控件设定DataContext, txtName.Context = p1;txtAge.Context = p1;
  3. Xaml中要进行数据绑定的属性Text="{Binding Name}" , 几乎所有的控件属性都这样数据绑定的
  4. Text="{Binding Name}" ,把控件的Text属性当顶到DataContext指向的p1对象的Name属性上来
  5. 由于普通对象没有"通知我的属性变了"这么一种机制,所以改变对象的属性界面不会变. 但是界面改变是有TextChanged之类的事件的,所以界面可以同步到修改对象.
  6. 如果要求后台对象的值发生改变界面跟着变,则需要类实现INotifyPropertyChanged接口,并且在属性值变化后触发事件;(一般不需要)
  7. 父类控件的数据绑定后,子类控件数据绑定都绑定响应的数据对象
  8. class Person : INotifyPropertyChanged
        {
              private   int   age;
              public   int   Age
            {
                  get
                {
                      return   age;
                }
                  set
                {
                      this .age =   value   ;
                      if   (PropertyChanged !=   null   )
                    {
                        PropertyChanged(   this ,   new   PropertyChangedEventArgs (   "Age" ));
                    }
                }
            }
              public   event   PropertyChangedEventHandler   PropertyChanged;
        }
二、相关的数据绑定基础(ListBox和DataGrid)
(一)ListBox使用
     ListBox的使用
     绑定时候使用ItemSource
     显示用DisplayMemberPath="具体属性名字"
      SelectedValuePath   选出来的值对应的属性
     默认是吧每一项的对象的ToString(), DisplayMemberPath显示具体属性
一些代码展示:
List < Person  > list =  new  List < Person  >();
             Person  p1 =  new  Person ();
            p1.Name =  "朱京辉"  ;
            p1.Age = 18;
            list.Add(p1);
           
            list.Add(  new  Person  () { Name =  "苏坤" , Age = 16 });
            list.Add(  new  Person  () { Name =  "黎明" , Age = 16 });

             //LsitBox绑定的是ItemSource属性
            lbPersons.ItemsSource = list;

private  void  btnShowLbItem_Click( object  sender,  RoutedEventArgs  e)
        {
             //SelectedItem获得的是选中行对应的对象
             object  selectedItem = lbPersons.SelectedItem;

             //SelectedValue获得是选中行对应对象的"SelectedValuePath"标志的属性值
             object  selectedValue = lbPersons.SelectedValue;

             if  (selectedValue !=  null  )
            {
                 MessageBox .Show(selectedValue.ToString());
            }
        }

(二)DataGrid使用
     默认Grid会自动生成列, 还会自动添加功能
     我们需要取消自动功能,使用的属性是AutoGenerateColumn="false"  CanUserAddRows="false"
     重要的是使用<DataGrid.Columns>  里面用专用的列属性 Header对应名字
     注意默认是非只读属性, IsReadOnly="true"  阻止用户修改数据
     DataGridTextBoxColumn  文本绑定
     DataGridCheckBoxColumn Header=" 列的名字" 直接绑定性别
     DataGridComberBoxColumn   x:Name="" (不能取名字的时候这样做)  这里使用选定项绑定

三、相关的SQL基础
具体参考SQL文章,已经已经详细地讲过了
下面是一些小摘要:
  1.  *like一定要慎用,会导致全部检索,效率太低,如果需要,可以借鉴'全文检索'技术
  2. ~~~博客园~~~~网站,有空可以去看看Lucene.Net 
  3. 数据库中的NULL表示不知道  唯一比较  select * from T_Table where name is NULL   (name=NULL是不可以的)
  4. 数据库连接池:  ado.net会尽可能地服用连接池中的链接, 不断关闭和打开不太会影响时间
四、学习ADO.NET
(一)基础知识
  1. ADO.NET: .NET中用来向数据库提交执行SQL语句的一堆类
  2. 本机访问直接"Windows验证",但是一般项目中都是单独的数据库服务器,程序在另外一台电脑上连接SQLServer在项目中,一般不会启用sa账户,这个是最高权限账户,应该设置一个受限制的账户
(二)理解SqlDataReader
  • SqlDataReader是连接相关的,SqlDataReader中的查询结果并不是放到程序中的,而是放在数据库服务器中,SqlDataReader只是相当于一个指针(游标), 只能读取当前游标指向的行,一旦连接断开再不能读取.这样做的好处是无论查询结果有多少条,对程序占用的内存几乎没有影响.
  • SqlDataReader对于小数据量的数据来说带来的只有麻烦. ADO.NET中提供了数据集的机制,将数据结果填充到本地内存中,这样连接断开,服务器断开都不影响数据的读取.数据集的好处是降低数据库服务器压力,编程也简单(其实原理大致还是用reader读取所有数据,储存在list中)
(三)编写SqlHelper文件
  • SqlHelper的作用,主要作用: 读取配置文件的连接数据库字符串,实现一些和数据库的参数查询函数,传入参数一般是sql命令和parameter[]参数数组
  • SqlHelper的一些问题,对于0参数 DataTable datatable = SqlHelper .ExecuteDataTable(sql, new SqlParameter[0]);
  • 注意NULL和DBNULL的区别
(四)经典练习——登陆练习
主要难点,和关键点分析
  • 登陆的密码错误的判断,“防御性编程”,有效防止非预期的错误
  • 账户锁定功能,输错密码3次,将账户对应数据库中错误次数加到3
  • 加入时间数据,根据距离最后一次输错时间差决定是否取消绑定
  • 改进: 加入DAL层;密码使用MD5加密
具体代码:
数据库设计:
use   [DB_Test]
Create   Table   [User2] (
         [ID]   [bigint]   identity (   1 , 1   )   primary   key ,
         [UserName]   [nvarchar]   ( max   )   NOT   NULL,
         [UserPwd]   [nvarchar]   ( max   )   NOT   NULL,
         [ErrorTimes]   [int]   ,
         [LastErrorTime]   [datetime]
);
Insert   Into   [User] (   UserName , UserPwd   , ErrorTimes )   Values (   'a' , 'aaa'   , 0 );
Insert   Into   [User] (   UserName , UserPwd   , ErrorTimes )   Values (   'b' , 'bbb'   , 0 );
Insert   Into   [User] (   UserName , UserPwd   )   Values   ( 'c' ,   'ccc' );

select   *   From   [User]

界面的设计代码:
< Grid >
          < Grid >
              < Grid.RowDefinitions >
                  < RowDefinition ></   RowDefinition >
                  < RowDefinition ></   RowDefinition >
                  < RowDefinition ></   RowDefinition >
              </ Grid.RowDefinitions >
              < Grid.ColumnDefinitions >
                  < ColumnDefinition ></   ColumnDefinition >
                  < ColumnDefinition ></   ColumnDefinition >
              </ Grid.ColumnDefinitions >
              < TextBlock   Text   ="用户名:"   FontSize ="30"   VerticalAlignment   ="Center"   HorizontalAlignment ="Center"   Grid.Row ="0"   Grid.Column   ="0"></ TextBlock >
              < TextBlock   Text   ="密码:"   FontSize ="30"   VerticalAlignment ="Center"   HorizontalAlignment ="Center"   Grid.Row ="1"   Grid.Column   ="0"></ TextBlock >
              < TextBox   Name   ="txtUserName"   Margin ="5"   Grid.Row ="0"   Grid.Column   ="1"></ TextBox >
              < PasswordBox   Name   ="txtUserPwd"     Margin   ="5"   Grid.Row ="1"   Grid.Column ="1"></ PasswordBox   >
              < Button   Name   ="btnLogin"   Content ="登录"   FontSize ="30"   Margin   ="5"   Grid.Row ="2"   Grid.Column ="0"   Click ="btnLogin_Click"></ Button >
              < Button   Name   ="btnExit"   Content ="退出"   FontSize ="30"   Margin   ="5"   Grid.Row ="2"   Grid.Column ="1"   Click ="btnExit_Click"></ Button >
          </ Grid >
      </ Grid >

主要的登录按钮的代码
           private   void   btnLogin_Click( object   sender,   RoutedEventArgs   e)
        {
              string   userName = txtUserName.Text;
              string   userPwd = txtUserPwd.Password;
              /************查询用户是否存在***************/
              string   sqlSearchUser =   "select * from T_User where UserName=@UserName"   ;
              DataTable   myTable =   SqlHelper   .ExecuteDataTable(sqlSearchUser,   new   SqlParameter ( "@UserName"   , userName));
              DataRowCollection   rows = myTable.Rows;
              if   (rows.Count <= 0)
            {
                  MessageBox .Show( "用户名不存在"   );
                  return ;
            }
              if   (rows.Count > 1)
            {
                  MessageBox .Show( "用户名重复,请联系管理员"   );
                  return ;
            }
              DataRow   row = rows[0];
              long   id = ( long   )row[ "ID" ];
              /******************检查错误登录次数和错误时间********************/
              int   ErrorTimes = 0;
              DateTime   LastErrorTime =   new   DateTime (2000,1,1);
              if   (row.IsNull( "ErrorTimes"   ))
            {
                  string   sql =   "update T_User set ErrorTimes=0 where ID=@id"   ;
                  SqlHelper .ExecuteNonQuery(sql,   new   SqlParameter (   "@id" , id));
            }
              else
            {
                ErrorTimes =   Convert .ToInt32(row[ "ErrorTimes"   ]);
            }
              if   (row.IsNull( "LastErrorTime"   ))
            {
                  DateTime   minTime =   new   DateTime (2000, 1, 1);
                  string   sql =   "update T_User set LastErrorTime=@errortime where ID=@id"   ;
                  SqlHelper .ExecuteNonQuery(sql,   new   SqlParameter (   "@errortime" , minTime),   new   SqlParameter ( "@id"   , id));
            }
              else
            {
                LastErrorTime = (   DateTime )row[ "LastErrorTime"   ];
            }
              if   (ErrorTimes < 0)
            {
                  string   sql =   "update T_User set ErrorTimes=0 where ID=@id"   ;
                  SqlHelper .ExecuteNonQuery(sql,   new   SqlParameter (   "@id" , id));
                ErrorTimes = 0;
            }
              if   (ErrorTimes >= 3)
            {
                  //上次的输入密码错误时间距离当前时间超过3小时,清零错误次数
                LastErrorTime = LastErrorTime.AddHours(3);
                  int   compare = LastErrorTime.CompareTo( DateTime .Now);
                  if   (compare <= 0)
                {
                      string   sql =   "update T_User set ErrorTimes=0 where ID=@id"   ;
                      SqlHelper .ExecuteNonQuery(sql,   new   SqlParameter (   "@id" , id));
                    ErrorTimes = 0;
                }
                  else
                {
                      MessageBox .Show( "您已经输错3次,抱歉,请于上次输错后3小时重试"   );
                      return ;
                }
            }
              /******************检验密码是否正确********************/
              string   truePwd =   Convert   .ToString(row[ "UserPwd" ]);
              if   (!userPwd.Equals(truePwd))
            {
                  /******************密码错误,记录时间,增加错误次数********************/
                  MessageBox .Show( "密码错误"   );
                  string   sql =   "update T_User set ErrorTimes=ErrorTimes+1,LastErrorTime=@now where ID=@id"   ;
                  SqlHelper .ExecuteNonQuery(sql,   new   SqlParameter (   "@now" ,   DateTime   .Now),   new   SqlParameter ( "@id"   , id));
                  return ;
            }
              else
            {
                  /******************密码正确登录********************/
                  MessageBox .Show( "恭喜你,登录成功"   );
                  string   sql =   "update T_User set ErrorTimes=0 where ID=@id"   ;
                  SqlHelper .ExecuteNonQuery(sql,   new   SqlParameter (   "@id" , id));
            }
        }
(四)经典练习——号码归属地查询练习
练习:从文本中读取号码归属地数据,并且存入到数据库中,再实现界面查询号码归属地
难点分析: 
  1. 从Text导入,使用文件操作或者流操作,这样操作的缺点很明显不适合大数据
  2. 使用**SqlBulkCopy 批量操作,批量写入数据库,大大提高效率(但是存在datatable占内存问题,需要解决使用read的方式读取就更加合理了)
  3. 对数据的分析,用SQL语句建数据库和表
  4. 在导入导出时候可能会导致窗体无法操作。使用线程可以很好地处理,自己在这里做了一个processBar和线程结合的一个模块,只要在处理函数的地方,写需要处理的函数,程序就可以在其他线程运行.问题:(1)无法把判断进度的函数加入到SqlBulkCopy中。(2)导出使用什么实现大数据的导出
自己写的一个小项目
数据库表结构:
use   [DB_Test]
CREATE   TABLE   [ T_PhoneNumDistrict ] (
         [ID]   [bigint]   identity (   1 , 1   )   primary   key ,
         [PhoneNum]   [nvarchar]   ( 10 )   NOT   NULL,
         [DistrictPlace]   [nvarchar]   ( 50 )   NOT   NULL,
         [NumType]   [nvarchar]   ( 20 )   NOT   NULL,
         [DistrictNum]   [nvarchar]   ( 10 )   NOT   NULL
)
批量导入
using(SqlBulkCopy bulkCopy = new SqlBulkCopy())
{
     bulkCopy.DestinationTableName = " T_PhoneNumDistrict";
     bulkCopy.ColumnMappings.Add("[表中对应的列名]"," [数据库中的字段名]");
     ...
     bulkCopy.WriteToServer(table);
}
五、三层构架学习
(一)基础知识
  • 这里讲的是精简的三层构架,主要是DAL层
  • 之前都是直接在ui层中邪SQL,对于大的项目这样做事很难维护的,而且复用性不强.三层构架是企业开发中常用设计模式,把数据库访问、业务逻辑、界面分离
  • 初学者直接学习三层构架会比较难,因此这次用精简的三层构架,只用DAL(Data Access Layer)层,把数据库访问封装到DAL中,UI调用到DAL中,原则"UI中不出现SQL"
  • 其他深入地内容: 三层构架/代码生成器
  • 编写Model(注意可空类型字段的考虑)
  • DAL常用的封装:ToModel、ListAll(对于大数据量的数据不要提供,而是提供条件搜索)、GetById、DeleteById、Update、AddNew
(二)项目注意点
(1)GUID的使用
  1. Guid可以当做唯一标示,Guid在SQL SERVER中是使用NEWID()方法产生guid值,并且存放的类型是uniqueidentifier
  2. 在C#中是Guid.NewGuid(); //产生一个Guid,值类型
  3.  (*)int自增字段的优点:占用空间小,无需开发人员干预,易读;缺点: 效率低,数据导入导出的时候很痛苦
  4. (*)Guid的优点:效率高,数据导入导出方便; 缺点:占用空间太大,不容易读懂(慢慢成为主流)
(2)密码处理
  1. 散列算法 MD5/DES/SHA
  2. MD5算法是不可逆的,也就是只能得到对应的md5值,不同的字符串对应的md5值相同的概率非常非常非常低
  3. MD5加盐处理,盐最好添加到app.config,方便用户修改
(3)构架基础
  1. 对于一个项目,最好新建一个数据库用户,该用户只能访问响应的数据库
  2. app.config 是放在UI项目中
  3. DAL层通过ConfigurationManager是可以读取到主项目的app.config的配置信息
  4. 引用关系: DAL项目引用Model,UI项目引用DAL和Model
(4)扩展,excel读写NPOI
  • 可以使用ExcelAutomation进行Excel文件的读写,但是需要电脑装上Excel,对Excel版本有要求,速度慢,有安全性,并发性问题(有空可以尝试怎么并发操作),不适合于网站类项目。
  • NPOI是一款轻量级的进行xls文件读写的开发包,完全是二进制读写,不依赖于Excel
  • 日期的处理: 日期需要设置CellType.NUMERIC类型,并且设置相应样式
  • //格式具体有哪些请看单元格右键中的格式,有说明
                  ICellStyle   styledate = workbook.CreateCellStyle();
                  IDataFormat   format = workbook.CreateDataFormat();
                styledate.DataFormat = format.GetFormat(   "yyyy\"年\"m\"月\"d\"日\""   );
      
  • ///   <summary>
              ///   NPOI的写入方法
              ///   </summary>
              private   void   NpoiWrite()
            {
                  HSSFWorkbook   hassworkbook =   new   HSSFWorkbook ();
                  ISheet   sheet1 = hassworkbook.CreateSheet( "第一页" );
                  IRow   rowHeader = sheet1.CreateRow(0); //第i行
                rowHeader.CreateCell(0,   CellType .STRING).SetCellValue( "单元格内容"   );
                  IRow   row2 = sheet1.CreateRow(2);
                row2.CreateCell(3,   CellType .STRING).SetCellValue( "呵呵"   );
                  //xls, xlsx
                  using   ( Stream   stream =   File .OpenWrite(   "c:/zjh.xls" ))
                {
                    hassworkbook.Write(stream);
                }
               
            }

              ///   <summary>
              ///   NPOI的读取方法
              ///   </summary>
              private   void   NpoiRead()
            {
                  using   ( Stream   stream =   File .OpenRead(   "c:/zjh.xls" ))
                {
                      HSSFWorkbook   workbook =   new   HSSFWorkbook (stream);
                      MessageBox .Show(workbook.GetSheetAt(0).GetRow(0).GetCell(0).ToString());
                }          
            }
NPOI开发包

(5)一些注意点
  • 数据的"软删除". 客户说的话不一定是你理解的话. 把数据真正删除在某些时候会有问题: "把员工删除了难道把员工填写的工资单都删除吗?",只是"离职"而已,部门不是删除,而是"停止".IsDeleted字段
  • 一个表引用另外一张表的时候一定要引用主键.
  • 用户的初始密 码和密码重置,解锁
  • 注意bit类型在sql语句中要写0,1;   在ado.net中用bool表示
  • 把一些可能会变的值写入app.config中
  • 扩展:在excel里面,  '23213231  ,在excel中显示字符串
  • 不能把看起来像数字的数字定义成int
  • 很多项目都不建立真正的外键,都是逻辑外键
  • TimeSpan 是DateTime相减, ts.TotalSecond, 差之后的秒数
  • 在计算的时候,先乘再除会相对精确
  • 当实现了IDisposable接口时,就要用using
(三)具体的人事管理系统分析

(1)一些注意点
  • 图片存储,暂时放在数据库中,用byte数组的方式存储,在网站开发一般是放在服务器上的
  • wpf comboBox阴影颜色  cmb.Effect = new DropShadowEffect() { Color = Colors .Red };
  • 图片存储在数据库中的设置成image格式的,对应是byte[]格式
  • 通过byte数组显示图片,用的是流操作,BitmapImage image = new BitmapImage ();image.StreamSource = new MemoryStream(byte[]) ; picbx.Source = image;     bmpImg.BeginInit();    bmpImg.EndInit();
  • 通过摄像头来来取得图片的时候,可以使用外部组件
(2)比较复杂的难点
  • 使用构架,DAL曾层使用
  • 数据表的主键使用guid,表的合并方便,效率高
  • 考虑,可空字段的赋值问题,涉及到DBNULL和null的差别
  • 删除数据使用软删除,尽可能保存数据
  • 密码加密,使用加盐的md5,对加密字符串进行加盐
  • 封装数据验证逻辑,判断添加的数据,是否所有的非空字段都有数值
  • 图片的存储和读取,在读取的时候,如果读取全部会引起内存消耗过大(在EmployeeDAL.cs文件41行)
  • 条件查询数据,这里使用了sql,自定义拼接,使用的还是parameter参数,所以不存在注入漏洞
  • 数据绑定对象,输入方便,修改方便
  • excel读写NPOI,
  • 多条件搜索, 全文检索(优化算法), 
  • 水晶报表的设计,连接,绑定
  • 系统设置存放在数据库中,优缺点详细参考后面


六、代码生成器编写
(一)分析
  1. 根据数据库连接字符串取得所有的表
  2. 根据选择的表获取所有的字段和字段类型
  3. 编写相应的Model代码
(二)一些关键点
  1. sql查询所有的表语句:  SELECT  TABLE_NAME  FROM  INFORMATION_SCHEMA,TABLES  WHERE  TABLE_TYPE = 'BASE TABLE'
  2. adapter.FillSchema(ds,SchemaType.Source);  具体不写可能会导致后面判断dbnull出错
  3. 不要随便使用try,catch,后面会有统一异常捕捉,连接异常是SqlException
  4. 查询表结构时,sql命令使用select top 0 from table_name; 
  5. 获取当前程序的目录地址, string currentDir = AppDomain.CurrentDomain.BaseDirectory;string configFile = System.IO.Path .Combine(currentDir, "connstr.txt");    //路径拼接
  6. 存储连接字符串信息,将第一次输入的连接字符串进行保存,下次打开后自动读取连接字符串
  7. 编写model,使用连接字符串,注意写类型的时候,判断可空类型.  在C#中值类型不可为空
  8. 有更好的方法如linq, T4模板
(二)自己编写的代码生成器
项目压缩包


七、系统功能
(一)系统日志
  • 系统日志管理:操作者、操作日期、操作描述。系统日志的符合搜索功能。操作日志(存在数据库中)、运行日志(存在文件中)。Log4Net
  • 配置管理
  • 异常处理: 不要进行无意义的try...catch. 只在真的需要catch的地方再处理.应该彻底在测试阶段消灭异常,程序写的好的话不应该会有未处理异常.
  • DispatcherUnhandledException
  • Application.Current.Properties   当前程序储存的键值对,相当于asp.net的session
  • 数据库中主要涉及id,OperatorId, MakeDate, ActionDesc这几个字段
(二)系统设置
  • 在数据库中设置三个字段:(id,  name,  value)
  • 配置信息存储在数据库,这样一些设置放在app.config,这个是比较系统的信息
  • 一些配置信息放在数据库,这样在其他电脑,非本地,存在服务器,这样更加方便
  • 程序中如果出现未处理异常的时候,统一处理异常 app.xaml中 DispatcherUnhandledException ="Application_DispatcherUnhandledException"事件处理未处理异常
(三)工资单生成
  •  降低单独难度,不具体讲解自定义工资项,固定是: 基本工资,奖金,绩效奖金,罚款几项,直接生成工资表的功能(生成之前判断是否实现已经存在).工资表允许编辑,并且打印工资条.
  • 进入系统,选择针对哪个月生成工资,选择部门(帐套),生成这个月下的部门的所有员工的初始工资单(如果已经生成则可以覆盖重新生成),然后可以手工调整,调整后打印工资表和工资条.
  • 工资表: Id,Year,Month,DepartmentId
  • 工资表明细: id,表头id, EmployeeId, 奖金, 基本工资, 罚款, 其他工资项   (主从表)
  • 工资在数据库中使用的类型是money, 对应C#中是decimal
  • 工资单的编辑和保存: 把binding设定为 UpdateSourceTrigger = PropertyChanged,编辑后在RowEditing事件中e.Row.DataContext获得修改后的对象.
(三)水晶报表
  • 添加水晶报表控件,程序集以SAPBussinessObjects开头
  • wpf项目所用的框架必须是“.net Framework4”, 不是4 clint ,要给app.config节点的startup增加一个属性:
  • 水晶报表的数字格式,修改问题,默认是千位有逗号,小数点有两位
  • 数据库专家的问题是: 无法访问项目外的类,很不合理,没有很好地解决方案

CrystalReportTest1   cry =   new   CrystalReportTest1 ();
 cry.SetDataSource(rpt);
 crystalReportsViewer.ViewerCore.ReportSource = cry;


(最后)出现的一些小问题、个人学习总结
     完成了人力资源管理系统,在这次的项目开发中,我学到的东西真的有很多很多,这是我第一次自己完成一个项目,在老师的指导下,一边学习一边写代码。在项目编写的过程中,遇到了很多问题,数据库怎么设计,三层构架的设计,全部是全新的内容,当我完成了整个项目后,打开程序,运行,那种成功的喜悦真的无法说明,各种激动啊。在完成这次小三层构架开发后,我终于可以继续下面asp.net的开发了,新的知识在等着我,我要启程了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值