開篇
我之前發過一篇博文《兩天完成一個小型工程報價系統(三層架構)》,不少朋友向我要源碼學習,后來久而久之忘記回復了。今天我再分享一個進銷存系統,只為學習,沒有復雜的框架和設計模式,有的是我個人的理解,大家互相探討技術才會提高。當然我的命名不是很規范,兄弟們湊合着看。:)
思想和架構
在傳統的三層架構思想上擴展出N層架構,將業務邏輯層換成WCF服務。抽象工廠的引入提高了程序的擴展性,單利+緩存+反射則提升了程序的性能。數據庫則換成了Oracle,所以相應的數據訪問層也換成了OracleDal,當然你完全可以寫SqlServerDal,因為我數據訪問層接口都已定義好。
界面和控件的設計美化
總體思路和流程---數據庫Oracle
數據庫既然選擇了Oracle,當然先必須安裝好Oracle,然后再裝Plsql,這一步也是很簡單的。不清楚的話,可去查找資料。
對Oracle而言,數據庫已服務形式存在,有幾個數據庫就對應幾個服務,刪除了數據庫后相應的服務也沒了,其次一個兼容程序監聽着服務。這些都可以自行配置,我不啰嗦了,畢竟我也不熟。我把Oracle腳本導出了,大家只要復制到Commad Window里粘貼即可,但前期創建表空間和用戶我還是稍微提一下:
- 首先用你用plsql連接一個服務(數據庫Orcl),用Connect as SysDBA進入。
- 創建表空間:注意路徑一定要已經存在。
create tablespace invoicing logging datafile 'C:\oracle\product\10.2.0\db_1\oradata\invoicing.dbf' size 32M autoextend on next 32M maxsize 1024M EXTENT MANAGEMENT LOCAL;
- 找到左下角側用戶(Users),創建用戶Invoicing,密碼:Invoicing,分配權限:dba,connect
- 用新創建的用戶名和密碼重新登錄。
- 找到Command Window,把我提供給你的腳本復制進去就OK了。
總體思路和流程---數據訪問層IDAL
- 一個通用的泛型接口:
public interface IBaseService<T> where T :class { List<T> GetEntities(string sqlWhere); T GetOneEntityByID(int id); T AddEntity(T entity); bool UpdateEntity(T entity); bool DeleteEntity(string sqlWhere); }
- 某個數據訪問層接口實繼承這個泛型接口
public interface ICommodityService:IBaseService<Model.CommodityModel> { }
總體思路和流程---抽象工廠Abstract
-
public abstract class DalFactory { public abstract IDAL.ICommodityService CommdityDal { get; } public abstract IDAL.IPurchaseCommodityService PurchaseCommdityDal { get; } public abstract IDAL.IPurchaseOrdersService PurchaseOrderDal { get; } public abstract IDAL.ISalesCommodityService SalesCommodityDal { get; } public abstract IDAL.ISalesOrdersService SalesOrderDal { get; } public abstract IDAL.IUserService UserDal { get; } }
總體思路和流程---數據訪問層Dal
- 為了提高效率,可以考慮緩存
public override IDAL.ICommodityService CommdityDal { //緩存里拿 get { OracleDAL.CommodityService instance = System.Web.HttpRuntime.Cache["CommodityDal"] as OracleDAL.CommodityService; if (instance == null) { instance = new OracleDAL.CommodityService(); System.Web.HttpRuntime.Cache.Add("CommodityDal", instance, null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null); } return instance; } }
- OracleHelper和System.Data.OracleClient來實現數據訪問層
namespace Insigma.Eyes.PSS.OracleDAL { public class CommodityService:ICommodityService { public List<Model.CommodityModel> GetEntities(string sqlWhere) { string sql = string.Format("select * from commodity where 1=1 {0}",sqlWhere); List<Model.CommodityModel> listCommodities = new List<Model.CommodityModel>(); //Using 限定對象使用的范圍在花括號里面,出了花括號后釋放資源 using (OracleDataReader odr=OracleHelper.ExecuteReader(OracleHelper.ConnectionString, CommandType.Text, sql, null)) { while (odr.Read()) { Model.CommodityModel commodity = new Model.CommodityModel(); commodity.ID = odr.GetInt32(0); commodity.Name = odr.IsDBNull(1) ? "" : odr.GetString(1); commodity.Type = odr.IsDBNull(2) ? "" : odr.GetString(2); commodity.Manufacturer = odr.IsDBNull(3) ? "" : odr.GetString(3); commodity.Inventory = odr.IsDBNull(4) ? 0 : odr.GetInt32(4); commodity.UnitPrice = odr.IsDBNull(5) ? 0 : odr.GetDecimal(5); commodity.Unit = odr.IsDBNull(6) ? "" : odr.GetString(6); listCommodities.Add(commodity); } } return listCommodities; } public Model.CommodityModel GetOneEntityByID(int id) { string sqlWhere = string.Format(" and id={0}",id); List<Model.CommodityModel> list = GetEntities(sqlWhere); return list.SingleOrDefault(c => c.ID == id); } private int GetNewID() { string sql = "select s_commodity.nextval from dual"; return int.Parse(OracleHelper.ExecuteScalar(OracleHelper.ConnectionString,CommandType.Text,sql,null).ToString()); } public Model.CommodityModel AddEntity(Model.CommodityModel entity) { entity.ID = GetNewID(); string sql = string.Format(@"insert into Commodity(ID,Name,Type,Manufacturer,Inventory,UnitPrice,Unit) values({0},'{1}','{2}','{3}',{4},{5},'{6}')", entity.ID, entity.Name, entity.Type, entity.Manufacturer, entity.Inventory, entity.UnitPrice, entity.UnitPrice); if (OracleHelper.ExecuteNonQuery(OracleHelper.ConnectionString,CommandType.Text,sql,null)>0) { return entity; } else { return null; } } public bool UpdateEntity(Model.CommodityModel entity) { string sql = string.Format("update Commodity set Name='{0}',Type='{1}',Manufacturer='{2}',Inventory={3},UnitPrice={4},Unit='{5}' where ID={6}", entity.Name, entity.Type, entity.Manufacturer, entity.Inventory, entity.UnitPrice, entity.Unit, entity.ID); return OracleHelper.ExecuteNonQuery(OracleHelper.ConnectionString,CommandType.Text,sql,null)>0; } public bool DeleteEntity(string sqlWhere) { string sql = string.Format("delete form Commodity where 1=1 {0}",sqlWhere); return OracleHelper.ExecuteNonQuery(OracleHelper.ConnectionString, CommandType.Text, sql, null) > 0; } } }
總體思路和流程---業務邏輯層WCF
- wcf是什么,最簡單理解就是接口,契約,當然你可以更加深入研究。我學的也不深。
namespace Insigma.Eyes.PSS.BLLWCFService { // 注意: 使用“重構”菜單上的“重命名”命令,可以同時更改代碼和配置文件中的接口名“ICommodityManagerService”。 [ServiceContract] public interface ICommodityManagerService { [OperationContract] [FaultContract(typeof(Exception))] CommodityModel[] GetCommodities(string name,string type,string manufacturer,string priceLow,string priceHigh); [OperationContract] CommodityModel[] GetCommoditiesByCondition(string condition); [OperationContract] CommodityModel GetOneCommodity(int id); [OperationContract] [FaultContract(typeof(Exception))] CommodityModel AddCommodity(CommodityModel oneCommodity); [OperationContract] [FaultContract(typeof(Exception))] bool UpdateCommodity(Model.CommodityModel commodity); } }
- 實現上面定義的接口契約:
public class PurchaseManagerService : IPurchaseManagerService { // private AbstractFactory.DalFactory dataFactory = null; public PurchaseManagerService() { dataFactory = DefaultProviderDal.DefaultDataProviderFactory; } public Model.PurchaseOrdersModel[] GetPurchaseOrders(string orderNumber, string orderDateStart, string orderDateEnd, string status) { string sqlWhere = ""; if (!string.IsNullOrWhiteSpace(orderNumber)) { sqlWhere += string.Format(" and orderNumber like '%{0}%'", orderNumber); } if (!string.IsNullOrWhiteSpace(orderDateStart)) { try { DateTime dt = DateTime.Parse(orderDateStart); sqlWhere += string.Format(" and orderDate>=to_date('{0}','yyyy-MM-dd')", dt.ToString("yyyy-MM-dd")); } catch { Exception oe = new Exception(); throw new FaultException<Exception>(oe, "查詢條件開始時間有誤!"); } } if (!string.IsNullOrWhiteSpace(orderDateEnd)) { try { DateTime dt = DateTime.Parse(orderDateEnd); sqlWhere += string.Format(" and orderDate<=to_date('{0}','yyyy-MM-dd')", dt.ToString("yyyy-MM-dd")); } catch { Exception oe = new Exception(); throw new FaultException<Exception>(oe, "查詢條件截至時間有誤!"); } } if (!string.IsNullOrWhiteSpace(status)) { sqlWhere += string.Format(" and Status='{0}'", status); } //IDAL.IPurchaseOrdersService purchaseOrdersService = new OracleDAL.PurchaseOrderService(); //return purchaseOrdersService.GetEntities(sqlWhere).ToArray(); return dataFactory.PurchaseOrderDal.GetEntities(sqlWhere).ToArray(); } public Model.PurchaseCommodityModel[] GetPurchaseCommoditiesByID(int purchaseID) { string sqlWhere = string.Format(" and PurchaseOrderID={0}",purchaseID);//看看數據庫里面的字段 //IDAL.IPurchaseCommodityService purchaseCommodityService =new OracleDAL.PurchaseCommodityService(); //return purchaseCommodityService.GetEntities(sqlWhere).ToArray(); return dataFactory.PurchaseCommdityDal.GetEntities(sqlWhere).ToArray(); } public Model.PurchaseCommodityModel AddPurchaseCommodityModel(Model.PurchaseCommodityModel onePurchaseCommodity) { //return new OracleDAL.PurchaseCommodityService().AddEntity(onePurchaseCommodity); return dataFactory.PurchaseCommdityDal.AddEntity(onePurchaseCommodity); } //幾個ID要分清楚 public bool PostPurchaseOrder(int id) { Model.PurchaseOrdersModel oneOrder=GetOnePurchaseOrder(id); if (oneOrder.Status.Equals("已入庫")) { Exception oe = new Exception(); throw new FaultException<Exception>(oe,"訂單已經提交,請務重復提交"); } List<Model.PurchaseCommodityModel> purchaseCommoditiesList = GetPurchaseCommoditiesByID(id).ToList(); IDAL.ICommodityService commodityService = new OracleDAL.CommodityService(); foreach (Model.PurchaseCommodityModel onePurchaseCommodity in purchaseCommoditiesList) { Model.CommodityModel commodityModel = new Model.CommodityModel(); commodityModel.ID = onePurchaseCommodity.CommodityID; commodityModel.Manufacturer = onePurchaseCommodity.CommodityManufacturer; commodityModel.Name = onePurchaseCommodity.CommodityName; commodityModel.Type = onePurchaseCommodity.CommodityType; commodityModel.Unit = onePurchaseCommodity.CommodityUnit; commodityModel.UnitPrice = onePurchaseCommodity.CommodityUnitPrice; commodityModel.Inventory = onePurchaseCommodity.CommodityInventory + onePurchaseCommodity.Count; //這兒不會出現異常了吧,否則要回滾 commodityService.UpdateEntity(commodityModel); } oneOrder.Status = "已入庫"; return new OracleDAL.PurchaseOrderService().UpdateEntity(oneOrder); } public Model.PurchaseOrdersModel GetOnePurchaseOrder(int id) { //return new OracleDAL.PurchaseOrderService().GetOneEntityByID(id); return dataFactory.PurchaseOrderDal.GetOneEntityByID(id); } public Model.PurchaseCommodityModel GetOnePurchaseCommoditiesByID(int purchaseCommodityID) { //return new OracleDAL.PurchaseCommodityService().GetOneEntityByID(purchaseCommodityID); return dataFactory.PurchaseCommdityDal.GetOneEntityByID(purchaseCommodityID); } public bool UpdatePurchaseCommodity(Model.PurchaseCommodityModel model) { //return new OracleDAL.PurchaseCommodityService().UpdateEntity(model); return dataFactory.PurchaseCommdityDal.UpdateEntity(model); } public bool DeletePurchaseCommodity(int id) { string sqlWhere = " and id=" + id; //return new OracleDAL.PurchaseCommodityService().DeleteEntity(sqlWhere); return dataFactory.PurchaseCommdityDal.DeleteEntity(sqlWhere); } public Model.PurchaseOrdersModel AddPurchaseOrder(Model.PurchaseOrdersModel purchaseOrder) { //return new OracleDAL.PurchaseOrderService().AddEntity(purchaseOrder); return dataFactory.PurchaseOrderDal.AddEntity(purchaseOrder); } public bool UpdatePurchaseOrder(Model.PurchaseOrdersModel onePurchaseOrder) { //return new OracleDAL.PurchaseOrderService().UpdateEntity(onePurchaseOrder); return dataFactory.PurchaseOrderDal.UpdateEntity(onePurchaseOrder); } public bool DeletePurchaseCommoditiesByPurchaseOrderID(int purchaseOrderID) { string sqlWhere = " and PurchaseOrderID=" + purchaseOrderID; //調用另一個模塊,調用BLL比較好 //return new OracleDAL.PurchaseCommodityService().DeleteEntity(sqlWhere); return dataFactory.PurchaseCommdityDal.DeleteEntity(sqlWhere); } public bool DeleteOrderID(int id) { string sqlWhere = string.Format(" and id={0}", id); //return new OracleDAL.PurchaseOrderService().DeleteEntity(sqlWhere); return dataFactory.PurchaseOrderDal.DeleteEntity(sqlWhere); } } }
-
dataFactory = DefaultProviderDal.DefaultDataProviderFactory;其實是個單利,我只要反射出一次工廠足以。
public class DefaultProviderDal { private static AbstractFactory.DalFactory instance = null; static DefaultProviderDal() { //string filePath = HttpContext.Current.Server.MapPath("~/DataProvider"); string filePath = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase; string dllFileName = System.Configuration.ConfigurationManager.AppSettings["DataProviderDllFile"]; string dalFactoryClassName = System.Configuration.ConfigurationManager.AppSettings["DataProviderFactoryName"]; System.Reflection.Assembly dll = System.Reflection.Assembly.LoadFile(filePath + "DataProvider\\" + dllFileName); instance = dll.CreateInstance(dalFactoryClassName) as AbstractFactory.DalFactory; } public DefaultProviderDal() { } public static AbstractFactory.DalFactory DefaultDataProviderFactory { get { return instance; } } }
總體思路和流程---UI
- 對WCF而言,實例化對象越多(如CommodityManagerServiceClient類的實例),對服務器壓力越大,所以也可以考慮單利。
public class WCFServiceBLL { //對WCF而言,對象實例化越多,對服務器壓力越大。 static BLLCommodity.CommodityManagerServiceClient commodityClient; static BLLSalesOrders.SalesManagerServiceClient salesClient; static BLLUsers.UserManagerServiceClient userClient; static BLLPurchaseOrders.PurchaseManagerServiceClient purchaseClient; public static CommodityManagerServiceClient GetCommodityService() { if (commodityClient==null) { commodityClient = new CommodityManagerServiceClient(); } if (commodityClient.State==CommunicationState.Closed) { commodityClient = new CommodityManagerServiceClient(); } if (commodityClient.State==CommunicationState.Faulted) { commodityClient = new CommodityManagerServiceClient(); } return commodityClient;
補充
由於數據庫之間的主外鍵關系以及多表查詢,為了方便,我創建了視圖,這和SqlServer里面是一樣的(Oracle里面是Create Or Replace),當然你也可以建立外鍵對象,我上個項目就是這么干的,當然ORM我們就不討論了。
create or replace view v_purchasecommodity as select pc.id,pc.purchaseorderid,pc.commodityid,c.name CommodityName, c.type commodityType,c.manufacturer CommodityManufacturer,c.inventory CommodityInventory, c.unitprice CommodityUnitPrice,c.unit CommodityUnit,pc.count,pc.purchaseprice,pc.totalprice from purchasecommodity pc inner join commodity c on pc.commodityid = c.id;
再補充一點:Oracle的自動增長列並不是像SqlServer那樣設置一下,就會自動增長,Oracle里面需要你自己創建Sequences,圖形操作也行,命令也行,我導出的Oracle腳本里面已經包含了相應的Sequences,應該可以看懂的。其余差別不大,相信你能看懂。
create sequence INVOICING.S_USERS minvalue 1 maxvalue 999999999999999999999999999 start with 1 increment by 1 cache 20;
關於界面美化的一些心得:
Winform程序功能很重要,但能提高用戶體驗那是最完美的,所以我用了一些圖標,設置相應的大小,當然用戶控件也是很重要的一部分,用戶控件嵌套在窗體里面是很簡單的:
public class LoadControls { public static void LoadInventory(Control parent) { parent.Controls.Clear(); Inventory inventory = new Inventory(); inventory.Dock = DockStyle.Fill; parent.Controls.Add(inventory); } public static void LoadPurchase(Control parent) { parent.Controls.Clear(); Purchase purchase = new Purchase(); purchase.Dock = DockStyle.Fill; parent.Controls.Add(purchase); } public static void LoadSales(Control parent) { parent.Controls.Clear(); Sales sales = new Sales(); sales.Dock = DockStyle.Fill; parent.Controls.Add(sales); }
總結
沒有什么猶豫就寫完了這篇博文,我把源代碼貢獻出來,當然這個例子只為學習,有需要的兄弟們可以拿去參考參考,大家多交流交流,才會相互促進進步,如果您覺得滿意的話,不放支持我一下,幫忙頂個,有動力才有精力寫出更好的博客,3x:)