bp(net core)+easyui+efcore實現倉儲管理系統——入庫管理之三存儲過程(三十九)

abp(net core)+easyui+efcore實現倉儲管理系統目錄

abp(net core)+easyui+efcore實現倉儲管理系統——EasyUI前端頁面框架 (十八)

abp(net core)+easyui+efcore實現倉儲管理系統——ABP WebAPI與EasyUI結合增刪改查之八(三十四)

 
 

   在上一篇文章Abp(net core)+easyui+efcore實現倉儲管理系統——入庫管理之二(三十八) 中我們創建了入庫單的一些有關DTO與分頁類由於入庫單我使用了到了資料庫的存儲過程,那麼本篇文章中我們來學習一下如何在ABP中調用存儲過程。

   我們都知道,倉儲管理系統中的單號最基本的要求就是唯一,這個條件必須滿足。或者說對於任何有單號的系統來說單號必須唯一,這是硬性要求。

   先來講講對於單號命名的幾種規則:

1、不重複。

  這點我相信大家都懂,單號的唯一性不用解釋。

2、安全性。

  你的單號編號盡量不要透露你公司的真實運營資訊,比如你的單號就是流水號的話,那麼別人就可以從單號推測出你公司的整體運營概括了。所以單號編碼必須是除了你們公司少部分人外,其他人基本看不懂的。其實最好的防泄漏編碼規則就是在編碼中不要加入任何和公司運營的數據。

3、隨機碼。

  很多人在制定單號編碼規則的時候,第一個想法肯定是不重複唯一性,那麼第二個想法可能就是安全性,同時滿足前兩者的第三個想法,就是在單號中添加隨機碼了。在單號中添加2~3隨機碼,和流水號結合使用,可以起到隱藏流水號的真實數據的作用。

4、防止並發。

  這條規則主要針對編碼中有時間的設定。

5、控制位數。

  這點很好理解,單號的作用就是便於查詢。

 

單號幾種常規的創建方式:

    1.利用資料庫主鍵值產生一個自增長的訂單號(訂單號即數據表的主鍵)

    2.日期+自增長數字的訂單號(比如:20200101100662、202002100662、2002100662

    3.隨機生成的單號(6512353245921)

    4.字母+數字字元串式,字母應該有特殊意義。如入庫單,GD202016652

 

  訂單號設計用戶體驗規則:

   1.訂單號無重複性;

   2.如果方便客服的話,最好是“日期+自增數”樣式的訂單號。

   3.訂單號長度盡量保持短(15位以內),方便用戶,長的號碼報錯幾率高,影響客服效率;

   4.如果你的系統用戶量在千萬級別,那麼訂單號盡量保持數字型(純整數),在資料庫訂單索引查詢中,長整數字型的數據索引與檢索效率,遠遠高於文本型。對於中小應用可以使用“字母+數字”的字元串形式!

 

五、使用存儲過程創建單號

   在使用ABP框架構項目時,如果想在倉儲層調用存儲過程,我們應該如何來實現呢?關於這個問題,我搜索了很多資料,最後還看了官方文檔:https://aspnetboilerplate.com/Pages/Documents/Articles/Using-Stored-Procedures,-User-Defined-Functions-and-Views/index.html

在看完官方文檔,對於如何在ABP中使用存儲過程已經有了一個相應的思路。現在我們來實現。

  1. Visual Studio 2017的“解決方案資源管理器”中,右鍵單擊“ABP.TPLMS.Core”項目的“IRepositories”文件夾,在彈出菜單中選擇“添加” >  “類”,在彈出對話框中選擇“介面”, 介面命名為 IInStockOrderRepository,然後選擇“添加”。如下圖。

 

    2.在IInStockOrderRepository介面定義我們需要用到的方法,程式碼如下。

using Abp.Domain.Repositories;  using System;  using System.Collections.Generic;  using System.Data;  using System.Data.SqlClient;  using System.Data.Common;  using System.Linq;  using System.Text;  using ABP.TPLMS.Entitys;  using System.Threading.Tasks;  using Abp.Dependency;    namespace ABP.TPLMS.IRepositories  {        public interface IInStockOrderRepository : IRepository<InStockOrder,int>, ITransientDependency      {            /// <summary>          /// 執行給定的命令          /// </summary>          /// <param name="sql">命令字元串</param>          /// <param name="parameters">要應用於命令字元串的參數</param>          /// <returns>執行命令後由資料庫返回的結果</returns>          int Execute(string sql, params object[] parameters);            /// <summary>          /// 創建一個原始 SQL 查詢,該查詢將返回給定泛型類型的元素。          /// </summary>          /// <typeparam name="T">查詢所返回對象的類型</typeparam>          /// <param name="sql">SQL 查詢字元串</param>          /// <param name="parameters">要應用於 SQL 查詢字元串的參數</param>          /// <returns></returns>            IQueryable<T> SqlQuery<T>(string sql, params object[] parameters);            DbCommand CreateCommand(string commandText, CommandType commandType, params object[] parameters);            /// <summary>          /// 創建單號          /// </summary>          /// <param name="name">單證名稱程式碼</param>          /// <returns></returns>            string GetNo(string name);            /// <summary>          /// 導入貨物資訊          /// </summary>          /// <param name="ids">導入貨物的ID集合</param>          /// <param name="no">單號</param>          void ImportCargo(string ids,string no);        }  }

 
     3. Visual Studio 2017的“解決方案資源管理器”中,右鍵單擊“ABP.TPLMS.EntityFrameworkCore”項目的“Repositories”文件夾,在彈出菜單中選擇“添加” >  “類” 命名為 InStockOrderRepository,並繼承IInStockOrderRepository介面。實現介面中的方法。程式碼如下。
using Abp.Data;  using Abp.Dependency;  using Abp.Domain.Entities;  using Abp.EntityFrameworkCore;  using ABP.TPLMS.Entitys;    using ABP.TPLMS.IRepositories;  using Microsoft.EntityFrameworkCore;  using System;  using System.Collections.Generic;    using System.Data;  using System.Data.Common;  using System.Data.SqlClient;    using System.Linq;  using System.Text;  using System.Threading;  using System.Threading.Tasks;      namespace ABP.TPLMS.EntityFrameworkCore.Repositories  {        public class InStockOrderRepository : TPLMSRepositoryBase<InStockOrder, int> ,IInStockOrderRepository, ITransientDependency      {            private readonly IActiveTransactionProvider _transactionProvider;            public InStockOrderRepository(IDbContextProvider<TPLMSDbContext> dbContextProvider) : base(dbContextProvider)          { }            protected InStockOrderRepository(IDbContextProvider<TPLMSDbContext> dbContextProvider, IActiveTransactionProvider transactionProvider)              : base(dbContextProvider)          {                _transactionProvider = transactionProvider;          }            public DbCommand CreateCommand(string commandText, CommandType commandType, params SqlParameter[] parameters)          {                EnsureConnectionOpen();              var dbFacade = Context.Database;                var connection = Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetDbConnection(dbFacade);              var command = connection.CreateCommand();              command.CommandText = commandText;              command.CommandType = commandType;              command.Transaction = GetActiveTransaction();                foreach (var parameter in parameters)              {                  command.Parameters.Add(parameter);                }              return command;            }            DbCommand IInStockOrderRepository.CreateCommand(string commandText, CommandType commandType, params object[] parameters)          {                EnsureConnectionOpen();              var dbFacade = Context.Database;              var connection = Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetDbConnection(dbFacade);                  var command = connection.CreateCommand();              command.CommandText = commandText;              command.CommandType = commandType;              command.Transaction = GetActiveTransaction();              foreach (var parameter in parameters)              {                  command.Parameters.Add(parameter);                }              return command;          }                private void EnsureConnectionOpen()          {                var dbFacade = Context.Database;              var connection = Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetDbConnection(dbFacade);                if (connection.State != ConnectionState.Open)              {                  connection.Open();              }            }            int IInStockOrderRepository.Execute(string sql, params object[] parameters)          {              throw new NotImplementedException();            }            private DbTransaction GetActiveTransaction()          {              return (DbTransaction)_transactionProvider.GetActiveTransaction(new ActiveTransactionProviderArgs              {                  {"ContextType", typeof(TPLMSDbContext) },                  {"MultiTenancySide", MultiTenancySide }              });            }               string IInStockOrderRepository.GetNo(string name)          {                SqlParameter[] parameters = {                   new SqlParameter("Name",System.Data.SqlDbType.NVarChar,10),                   new SqlParameter("BH", System.Data.SqlDbType.NVarChar,20)                                              };                parameters[0].Value = name;              parameters[1].Direction = System.Data.ParameterDirection.Output;                int cnt = Context.Database.ExecuteSqlCommand(   "EXEC p_NextBH @Name, @BH output",  parameters);                string no = parameters[1].Value.ToString();                if (cnt < 0)              {                  no = string.Empty;              }              return no;          }                void IInStockOrderRepository.ImportCargo(string ids,string no)          {                SqlParameter[] parameters = {                  new SqlParameter("id",System.Data.SqlDbType.VarChar,500),                  new SqlParameter("No", System.Data.SqlDbType.NVarChar,20)             };                parameters[0].Value = ids + ",";              parameters[1].Value = no;              int cnt = Context.Database.ExecuteSqlCommand(   "EXEC SP_ImportCargo2GDE @id, @No",  parameters);          }            IQueryable<T> IInStockOrderRepository.SqlQuery<T>(string sql, params object[] parameters)          {              throw new NotImplementedException();          }      }  }     

   4.在這裡我一共使用了兩個存儲過程,p_NextBH SP_ImportCargo2GDE

   5.定義一張表TPLMS_NO,專門用來存放存所有需要唯一單號的單號的類型,以及類單號當前所使用到最大值。

CREATE TABLE [dbo].[TPLMS_NO](  [Name] [nvarchar](10) NOT NULL,  [Head] [nvarchar](10) NOT NULL,  [CurrentNo] [int] NOT NULL,  [BHLen] [bigint] NOT NULL,  [IsYear] [int] NOT NULL,  [DESCRIPTION] [nvarchar](50) NULL,  PRIMARY KEY CLUSTERED  (  [Name] ASC  )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]  ) ON [PRIMARY]    GO  ALTER TABLE [dbo].[TPLMS_NO] ADD  DEFAULT ('') FOR [Head]    GO  ALTER TABLE [dbo].[TPLMS_NO] ADD  DEFAULT ((0)) FOR [CurrentNo]    GO  ALTER TABLE [dbo].[TPLMS_NO] ADD  DEFAULT ((6)) FOR [BHLen]    GO  ALTER TABLE [dbo].[TPLMS_NO] ADD  DEFAULT ((1)) FOR [IsYear]    GO  INSERT INTO [TPLMS_NO]([Name],[Head],[CurrentNo] ,[BHLen],[IsYear],[DESCRIPTION]) VALUES  ('GDE','GD',0,6,1,'入庫單')    GO  INSERT INTO [TPLMS_NO]([Name],[Head],[CurrentNo],[BHLen],[IsYear],[DESCRIPTION])  VALUES ('BAT','A' ,0,7,0,'批次號')    GO

       6.由於這是一個小應用,所以單號的生成就是字母+日期+流水號。通過p_NextBH來實現單號的創建,專門用來在上一步的表中取單號。p_NextBH這個存儲過程的實現如下:

 

--獲取新編號的存儲過程    CREATE PROC [dbo].[p_NextBH]  @Name nvarchar(10),           --編號種類  @BH nvarchar(20) OUTPUT --新編號  AS    BEGIN TRAN  UPDATE [TPLMS_NO] WITH(ROWLOCK) SET  @BH=Head+case isyear when 1 then convert(varchar(4),year(getdate())) else '' end  +RIGHT(POWER(convert(bigint,10),BHLen)+CurrentNo+1,BHLen),  CurrentNo=CurrentNo+1  WHERE Name=@Name  select @BH    COMMIT TRAN  GO

     7. 關於使用p_NextBH這個存儲過程生成單號有什麼優缺點呢?在存儲過程中使用事物,資料庫的性能會急劇下滑。對於小應用來說,這並不是太大的問題,對於中大型應用來說,就可能是問題了。可以直接使用UPDATE獲取到的更新鎖,即SQL SERVER會保證UPDATE的順序執行。適用中型應用,但是無法滿足高並發性能要求。我們來改一下存儲過程。

--獲取新編號的存儲過程  CREATE PROC [dbo].[p_NextBH]  @Name nvarchar(10),           --編號種類  @BH nvarchar(20) OUTPUT --新編號    AS    UPDATE [TPLMS_NO] WITH(ROWLOCK) SET  @BH=Head+case isyear when 1 then convert(varchar(4),year(getdate())) else '' end  +RIGHT(POWER(convert(bigint,10),BHLen)+CurrentNo+1,BHLen),  CurrentNo=CurrentNo+1  WHERE Name=@Name    select @BH    GO

 

 

    8. 通過傳遞貨物資訊的ID,把貨物資訊導入到入庫單中,這個功能通過存儲過程SP_ImportCargo2GDE來實現。這個存儲過程的實現如下:

CREATE Proc [dbo].[SP_ImportCargo2GDE]  @id varchar(1000),           --id集合  @No nvarchar(20)  --單號    as      CREATE TABLE #IdTable(Id int  NULL)    DECLARE @PointerPrev int      DECLARE @PointerCurr int      DECLARE @TName nvarchar(100)      Set @PointerPrev=1      while (@PointerPrev < LEN(@id))      Begin            Set @PointerCurr=CharIndex(',',@id,@PointerPrev)          if(@PointerCurr>0)          Begin                set @TName=SUBSTRING(@id,@PointerPrev,@PointerCurr-@PointerPrev)         --如果作為查詢條件,我需要創建一個臨時表,將數據插入進去           insert into #IdTable (Id) VALUES (convert(int,@TName))           SET @PointerPrev = @PointerCurr+1            End          else              Break      End    DECLARE @BH nvarchar(20),@batch varchar(20),@maxseqno int  select @BH=@No  select @maxseqno=isnull(MAX(seqno),0) from [InStockOrderDetail] where InStockNo= @BH    --創建批次號    EXEC [dbo].[p_NextBH] 'BAT', @batch OUTPUT    INSERT INTO [dbo].[InStockOrderDetail]             ([InStockNo],[SeqNo],[SupplierId],[CargoCode],[HSCode],[CargoName],[Spcf]             ,[Unit],[Country],[Brand] ,[Curr],[Package],[Length],[Width],[Height],[Qty]             ,[Vol],[LawfQty],[SecdLawfQty],[Price],[TotalAmt],[GrossWt],[NetWt]             ,[LawfUnit] ,[SecdLawfUnit],[Batch],[DeliveryOrderDetailId],[CreationTime])       SELECT @BH,convert(int,seqno)+@maxseqno,a.supplierid,[CargoCode],[HSCode],[CargoName],[Spcf]           ,[Unit],[Country],[Brand],[Curr]  ,[Package],[Length],[Width],[Height],0 [Qty]   ,[Vol] ,0 [LawfQty], 0 [SecdLawfQty] ,[Price],0 [TotalAmt],[GrossWt],[NetWt]   ,'' [LawfUnit],'' [SecdLawfUnit],@batch,a.id,getdate()    FROM    (select row_number() OVER  ( order by id) seqno,* from [dbo].Cargos     where id in (select id from #IdTable    where  id not in (select [DeliveryOrderDetailId]    from [InStockOrderDetail]    where InStockNo= @BH    )    )    ) a    drop table #IdTable    GO

 

    9. 關於單號的創建,除了使用存儲過程,也可以使用應用程式來創建。不過使用應用程式來創建,你要保證應用的高可用性,並且建議把最大值保存到資料庫。我在這裡只是給出大概的程式碼。

public class BillNoBuilder{      private static object locker = new object();        private static int seq = 0;        public static string NextBillNumber(string head){         //在這裡執行,或是經過一定的步長之後,再執行。GetMaxSeq();          lock(locker){              if(seq == 99999999)                  seq = 0;              else                  seq++;                return head+DateTime.Now.ToString("yyyyMMdd") + sn.ToString().PadLeft(8, '0');          }  }    //獲取資料庫中最大的序列號  private static void GetMaxSeq()  {  //seq =資料庫中的最大值    }          // 防止創建類的實例      private BillNoBuilder(){}  }