幾年前跟隨專案經理做的一個ERP小專案,自己業餘時間整理的開發手冊,供參考。
開發環境配置:程式設計環境為Microsoft Visual Studio 2010,資料庫是SQL Server 2008 R2。設計架構Windows Forms+ .NET Remoting + SQL Server,所有程式的程式碼量(框架,工具,業務邏輯)在5萬行以內。
1 SQL Server 資料庫表設計
設計供應商表Vendor, tb是通用字首識別符號號。FM是資金管理Finance Management。
CREATE TABLE [dbo].[tbFMVendor](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Code] [nvarchar](50) NULL,
[Name] [nvarchar](50) NULL,
[Description] [nvarchar](50) NULL,
CONSTRAINT [PK_tbVendor] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
iBatis框架要求每個表要以Id作為主鍵,型別為(C#:Int64, Sql:bigint)是個自增型。因為Id是唯一的,所以從表不需要設計多主鍵與主表關聯。
如果是從表(明細表),則需要新增對主表的Id列的外來鍵引用。參考下面的指令碼例子。
ALTER TABLE [dbo].[tbCustomerDiscBankAcct] ADD CONSTRAINT [FK_tbCustomerDiscBankAcct_tbCustomer] FOREIGN KEY ([IdCustomer]) REFERENCES [dbo].[tbCustomer] ([Id])
2 設計iBatis對映檔案
新增Xml對映檔案,放置於ErpMappingClass\Model\SqlMap目錄中,同時設定它的生成動作(Build Action)是嵌入式資源(Embedded Resource)
開啟FMVendor.xml檔案,增加內容如下所示,namespace的值為實體型別名稱
<sqlMap namespace="FMVendor" xmlns="http://ibatis.apache.org/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<alias>
<typeAlias alias="FMVendor" type="Erp.Model.FMVendor"/>
</alias>
type的值為實體型別定義的完整名稱。
在Xml檔案中增加不同的節(Element),用於SQL語句對資料的增刪查改。
資料操作語句 |
Xml片段寫法 |
讀取 |
<select id="Select" parameterClass="Hashtable" resultClass="FMVendor" > |
插入新資料 |
<insert id="Insert" parameterClass="FMVendor" resultClass="Erp.Model.BaseClass" > |
更新資料 |
<update id="Update" parameterClass="FMVendor" > |
刪除資料 |
<delete id="Delete" parameterClass="Hashtable"> delete $Tablename$ where Id=#Id# </delete> |
注意引數的寫法:ColumnName=#ColumnName# 以”#”表示列名對應的引數。
刪除資料的Xml程式碼在BaseClass對應的Xml節中有配置,此處不用寫。
3 設計實體型別及資料增刪查改
實體型別的程式碼根據Code Smith模板自動生成,同時加上Serializable以支援用於遠端呼叫時物件的可序列化。
3.1 根據資料庫表的列,生成實體型別定義
[Serializable]
public class FMVendor: BaseClass
{
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
重寫BaseClass的tm_getTableName方法,返回實體對映的資料庫表名稱。
protected override string tm_getTableName()
{
return "tbFMVendor";
}
3.2 讀取一個實體物件(三個過載方法,前兩個方法傳入不同的引數呼叫第三個方法)
public static FMVendor getFMVendorById(Int64 _Id)
{
Hashtable htFilter = new Hashtable();
htFilter.Add("Id", _Id);
return getFMVendorByFilter(htFilter);
}
public static FMVendor getFMVendorByCode(String _Code)
{
Hashtable htFilter = new Hashtable();
htFilter.Add("Code", _Code);
return getFMVendorByFilter(htFilter);
}
public static FMVendor getFMVendorByFilter(Hashtable _htFilter)
{
FMVendor fmVendor = Select<FMVendor>(_htFilter);
return fmVendor;
}
3.3 讀取一個實體集合(兩個過載方法,前一個方法傳入空值呼叫第二個方法)
public static IList<FMVendor> getAllFormKind()
{
return getAllFormKindByFilter(null);
}
public static IList<FMVendor> getAllFormKindByFilter(Hashtable _htFilter)
{
IList<FMVendor> list = Global.QueryForList<FMVendor>("FMVendor.Select", _htFilter);
return list;
}
3.4 儲存資料 儲存前做資料驗證,驗證資訊以DataResult物件傳回
public static DataResult saveFMVendor(ref FMVendor _fmVendor, String _userCode)
{
DataResult result = new DataResult(false);
if (string.IsNullOrEmpty(_fmVendor.Code))
{
result.Succeed = false;
result.Message = "請輸入供應商型別程式碼! ";
return result;
}
if (string.IsNullOrEmpty(_fmVendor.Name))
{
result.Succeed = false;
result.Message = "請輸入供應商型別名稱! ";
return result;
}
//資料校效
User user = User.getUserByCode(_userCode);
FMVendor fmVendor = getFMVendorById(_fmVendor.Id);
if (fmVendor == null)
{
_fmVendor.Id = 0;
}
FMVendor formkindExist = getFMVendorByCode(_fmVendor.Code);
if (formkindExist != null && formkindExist.Id != _fmVendor.Id)
{
result.Succeed = false;
result.Message = "供應商程式碼重複,請重新輸入一個新程式碼!";
return result;
}
try
{
Global.BeginTransaction();
_fmVendor.Save();
Global.CommitTransaction();
result.Succeed = true;
return result;
}
catch (Exception ex)
{
Global.RollBackTransaction();
result.Succeed = false;
result.Message = ex.Message;
return result;
}
}
3.5 刪除資料
public static String deleteFMVendor(Int64 vendorId)
{
DataResult result = new DataResult();
try
{
FMVendor fmVendor = GsctErp.Model.FMVendor.getFMVendorById(vendorId);
if (fmVendor != null)
{
fmVendor.Delete();
}
return "刪除成功 ! ";
}
catch (Exception ex)
{
return "刪除失敗 ! " + ex.Message;
}
}
FMVendor.xml檔案中不需要寫SQL刪除資料語句,基型別BaseClass中已經有刪除方法實現。
4 設計遠端物件(.NET Remoting)
遠端物件需要公開相應的資料訪問方法給客戶端介面呼叫。
public class NroFMVendor: BaseClass
{
}
遠端物件命名規則在原有的物件名稱前加Nro字首,並繼承於BaseClass,它的方法是對實體物件的資料訪問的封裝,每個方法的第一行是使用者驗證程式碼。
public FMVendor getFMVendorById(Int64 _Id)
{
User user = ValidateIdentity();
return FMVendor.getFMVendorById(_Id);
}
public FMVendor getFMVendorByCode(string Code)
{
User user = ValidateIdentity();
return FMVendor.getFMVendorByCode(Code);
}
public DataResult saveFMVendor(ref FMVendor vendor, String _userCode)
{
User user = ValidateIdentity();
return FMVendor.saveFMVendor(ref vendor, _userCode);
}
修改型別Erp.Model. FlexFactory,在該型別中增加私有靜態變數
增加公共屬性,以用於客戶端的介面訪問,需要增加的程式碼如下所示
public class FlexFactory
{
private static NroFMVendor nroFMVendor;
public static NroFMVendor myNroFMVendor
{
get {
if (nroFMVendor == null)
nroFMVendor = ActivatorGetObject<NroFMVendor>();
return nroFMVendor;
}
}
}
修改型別Erp.Model.FlextFactory的RegisterService方法,公開遠端服務。
public class FlexFactory
{
RemotingConfigurationRegisterWellKnownServiceType<NroFMVendor>();
5 介面開發
在專案中增加窗體FrmDtlVendor.cs,繼承於FrmDtlBase。
public partial class FrmDtlVendor : FrmDtlBase
{
public FrmDtlVendor()
{
InitializeComponent();
}
private FMVendor _vendor;
public void setVendor(FMVendor vendor)
{
_vendor = vendor;
}
它是對單筆資料進行操作,介面如下所示
ERP系統中預定義的窗體基型別列表如下,可根據業務需要繼承。
型別名稱 |
用途 |
FrmLstBase |
以列表形式呈現資料 |
FrmDtlBase |
單筆資料的編輯(增刪查改)操作 |
FrmSchClass |
資料搜尋窗體 |
FrmRptFlt |
報表引數值選擇 |
FrmScnClass |
查詢方案 |
FrmImpBase |
資料匯入 |
回到FmVendorDtl窗體中,在OnLoad方法載入資料。
protected override void OnLoad(EventArgs e)
{
this.Text = SysParam.ClientSysTitle;
setRight();
FMVendor fmVendor = _vendor == null ? null : GsctFactory.myNroFMVendor.getFMVendorById(_vendor.Id);
if (fmVendor == null)
clearForm();
else
showVendor(fmVendor);
bindProfile();
this.CenterToParent();
}
有二種方法啟動這個窗體,當從List列表進入時,根據傳入的物件值載入資料,並繫結到介面控制元件中,同時設定許可權,對控制元件進行隱藏或是禁用處理。
增加資料儲存程式碼,示例方法如下所示
private void tsbSave_Click(object sender, EventArgs e)
{
DataResult result = saveVendor();
if (result.Succeed)
{
showVendor(_vendor);
MessageBox.Show("儲存成功!");
}
else
{
MessageBox.Show("儲存失敗!\r\n" + result.Message);
}
}
saveVendor方法中的物件儲存程式碼是呼叫遠端物件的方法,片段如下
try
{
vendor.Code = txtCode.Text.Trim();
vendor.Name = txtName.Text;
result = GsctFactory.myNroFMVendor.saveFMVendor(ref vendor, User.currUser.Code);
if (!result.Succeed)
throw new Exception(result.Message);
}
catch (Exception ex)
{
result.Message = ex.Message;
return result;
}
專案總結
1 iBatis是輕量型ORM,可以將SQL語句返回的結果繫結到物件實體,不過手寫SQL語句和增加實體定義檔案這兩步需要開發人員自己完成。所以需要另外開發Code Smith模板生成程式碼。
2 專案很小,僅限於公司內部員工使用,欠缺很多商業性ERP的特性,歡迎批評指正。