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. 散列算法 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的開發了,新的知識在等着我,我要啓程了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章