abp(net core)+easyui+efcore实现仓储管理系统——EasyUI前端页面框架 (十八) html
abp(net core)+easyui+efcore实现仓储管理系统——ABP WebAPI与EasyUI结合增删改查之八(三十四) 前端
在上一篇文章Abp(net core)+easyui+efcore实现仓储管理系统——入库管理之二(三十八) 中咱们建立了入库单的一些有关DTO类与分页类。因为入库单我使用了到了数据库的存储过程,那么本篇文章中咱们来学习一下如何在ABP中调用存储过程。sql
咱们都知道,仓储管理系统中的单号最基本的要求就是惟一,这个条件必须知足。或者说对于任何有单号的系统来讲单号必须惟一,这是硬性要求。数据库
先来说讲对于单号命名的几种规则:安全
一、不重复。架构
这点我相信你们都懂,单号的惟一性不用解释。并发
二、安全性。框架
你的单号编号尽可能不要透露你公司的真实运营信息,好比你的单号就是流水号的话,那么别人就能够从单号推测出你公司的总体运营归纳了。因此单号编码必须是除了大家公司少部分人外,其余人基本看不懂的。其实最好的防泄漏编码规则就是在编码中不要加入任何和公司运营的数据。ide
三、随机码。高并发
不少人在制定单号编码规则的时候,第一个想法确定是不重复惟一性,那么第二个想法可能就是安全性,同时知足前二者的第三个想法,就是在单号中添加随机码了。在单号中添加2~3随机码,和流水号结合使用,能够起到隐藏流水号的真实数据的做用。
四、防止并发。
这条规则主要针对编码中有时间的设定。
五、控制位数。
这点很好理解,单号的做用就是便于查询。
单号几种常规的建立方式:
1.利用数据库主键值产生一个自增加的订单号(订单号即数据表的主键)
2.日期+自增加数字的订单号(好比:2020010110066二、20200210066二、2002100662)
3.随机生成的单号(6512353245921)
4.字母+数字字符串式,字母应该有特殊意义。如入库单,GD202016652
订单号设计用户体验规则:
1.订单号无重复性;
2.若是方便客服的话,最好是“日期+自增数”样式的订单号。
3.订单号长度尽可能保持短(15位之内),方便用户,长的号码报错概率高,影响客服效率;
4.若是你的系统用户量在千万级别,那么订单号尽可能保持数字型(纯整数),在数据库订单索引查询中,长整数字型的数据索引与检索效率,远远高于文本型。对于中小应用可使用“字母+数字”的字符串形式!
5、使用存储过程建立单号
在使用ABP框架构建项目时,若是想在仓储层调用存储过程,咱们应该如何来实现呢?关于这个问题,我搜索了不少资料,最后还看了官方文档:https://aspnetboilerplate.com/Pages/Documents/Articles/Using-Stored-Procedures,-User-Defined-Functions-and-Views/index.html
在看完官方文档,对于如何在ABP中使用存储过程已经有了一个相应的思路。如今咱们来实现。
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); } }
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
--获取新编号的存储过程 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(){} }