C#中调用存储过程的简单示例

0. 简介

【定义】:存储过程(Stored Procedure) 是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,它存储在数据库中,一次编译后永久有效,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。

【优缺点】:存储过程优缺点都非常的明显!几乎每一篇讨论存储过程的文字,都是会说其他优点是balabala,缺点是balabala,然而最后作者的结论都是:“我不推荐使用存储过程”

具体的存储过程的优缺点这里就不详述了!

公司旧项目使用存储过程实现业务逻辑,没办法只能研究了一下 🙃!

闲言碎语不要讲,书归正传,下面就开始存储过程!

1. 语法细节

变量与变量之间使用逗号隔开,语句结尾无标点符号

声明变量:declare @variate_name variate_type,例如声明并赋值:declare @name nvarchar(50) ='shanzm'

变量赋值:set @variate_name =value

打印变量:print @variate_name

begin……end 之间的SQL语句称之为一个代码块

可以使用if……else实现逻辑判断

创建存储过程:create procedure pro_name

执行存储过程:execute pro_name

输出参数:存储过程返回的是SQL语句查阅结果,在定义参数后,添加output,设置为一个输出参数(和C#中输出参数类似),相当于多了一个返回值!

创建存储过程的基本形式:

createprocedurepro_name_tableName@param1 param1_type =defaultValue,--声明参数的时候可以设置默认参数@param2 param2_type,asbeginSETNOCOUNTON;--关闭返回受影响行数--sql语句endgo

2. 示例1:模拟转账

①示例背景:使用存储过程,模拟在一张存款表中实现用户与用户之间的转账

②准备工作1:在数据库中创建表szmBank

CREATETABLE[dbo].[szmBank]([Id] [bigint]IDENTITY(1,1)NOTNULL,[Balance] [decimal](18,0)NOTNULL)

添加一些测试数据:

Id                  Balance

---------------      ----------------

1                    1000

2                    2000

3                    3000

③准备工作2:封装C#代码中的SQL辅助类SqlHelper

注意封装的时候要有一个CommandType参数,决定是执行SQL语句还是存储过程,

CommandType是一个枚举类型,其中Text值为执行SQL语句,StoreProcedure为执行存储过程

具体封装细节这里就不详述了。

找到了2年前我封装的一个SqlHelper.cs,常规使用没有任何问题,仅供参考:

#region// ===============================================================================// Project Name        :    // Project Description : // ===============================================================================// Class Name          :    SqlHelper// Class Version      :    v1.0.0.0// Class Description  :    SQL语句辅助类// CLR                :    4.0.30319.18408  // Author              :    shanzm// Create Time        :    2018-8-14 18:22:59// Update Time        :    2018-8-14 18:22:59// ===============================================================================// Copyright © SHANZM-PC  2018 . All rights reserved.// ===============================================================================#endregionusingSystem.Configuration;usingSystem.Data;usingSystem.Data.SqlClient;namespace_16StoreProcedure{publicclassSqlHelper{privatestaticreadonlystringconnStr =            ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;///<summary>///返回查询结果的的表///</summary>///<param name="sql">SQL语句或存储过程</param>///<param name="type">执行类型</param>///<param name="param">参数</param>///<returns></returns>publicstaticDataTableGetDataTable(stringsql, CommandType type,paramsSqlParameter[] param){using(SqlConnection conn =newSqlConnection(connStr))            {using(SqlDataAdapter adapter =newSqlDataAdapter(sql, conn))                {if(param !=null)                    {                        adapter.SelectCommand.Parameters.AddRange(param);                    }                    adapter.SelectCommand.CommandType = type;                    DataTable da =newDataTable();                    adapter.Fill(da);returnda;                }            }        }///<summary>///返回影响行数///</summary>///<param name="sql">SQL语句或存储过程</param>///<param name="type">执行类型</param>///<param name="param">参数</param>///<returns></returns>publicstaticintExecuteNonquery(stringsql, CommandType type,paramsSqlParameter[] param){using(SqlConnection conn =newSqlConnection(connStr))            {using(SqlCommand cmd =newSqlCommand(sql, conn))                {if(param !=null)                    {                        cmd.Parameters.AddRange(param);                    }                    cmd.CommandType = type;                    conn.Open();returncmd.ExecuteNonQuery();                }            }        }///<summary>///返回查询结果的第一行第一个单元格的数据///</summary>///<param name="sql">SQL语句或存储过程</param>///<param name="type">执行类型</param>///<param name="param">参数</param>///<returns></returns>publicstaticobjectExecuteScalar(stringsql, CommandType type,paramsSqlParameter[] param){using(SqlConnection conn=newSqlConnection (connStr ))            {using(SqlCommand cmd=newSqlCommand (sql,conn))                {if(param !=null)                    {                        cmd.Parameters.AddRange(param);                    }                    cmd.CommandType = type ;                    conn.Open();returncmd.ExecuteScalar();                }            }        }    }}

④编写存储过程

在数据库中:指定数据库-->可编程性-->存储过程-->右键:新建-->存储过程:

SQL Server中编写的SQL语句没有默认的格式化,所有代码排版按照我自己习惯进行Tab缩进

建议放到编辑器中查看下面的存储过程,会好看一些!

SQL大小写不敏感,我习惯小写,方便阅读!

-- =============================================-- Author: shanzm-- Create date: 2020年5月2日 19:56:51-- Description: 模拟账户之间转账-- =============================================createprocedurepro_transfer_szmbank@frombigint,@tobigint,@balancedecimal(18,0),@returnNumintoutput--(1表示转账成功,2表示失败,3表示余额不足)asbegin--判断转出账户是否有足够的金额declare@moneydecimal(18,0)select@money=Balancefromdbo.szmBankwhereId=@from; if @money-@balance>=0.1--开始转账beginbegintransactiondeclare@sumint=0--转出账户扣钱updateszmBanksetbalance=balance-@balancewhereid=@fromset@sum=@sum+@@error--转入账户加钱updateszmBanksetbalance=balance+@balancewhereid=@toset@sum=@sum+@@error--判断是否成功if@sum<>0beginset@returnNum=2--转账失败rollbackendelsebeginset@returnNum=1--转账成功commitendendelsebeginset@returnNum=3--余额不足endendgo

在数据库中执行测试(F5):

--执行测试:declare@retintexecutepro_transfer_szmbank @from='1',@to='2',@balance='10',@returnNum=@retoutput--注意1.输出参数在执行语句中也是要表明"output"--注意2.输入输出参数也是可以不赋值的,这里就没有赋值,但是需要定义一个变量用于存储输出值,这里就是@ret(和使用输出参数out的方式一样)--注意3.是@returnNum=@ret,而不@ret=@returnNum,这里就和函数中给参数赋值是一样的,参数=valueprint @ret--结果是打印:1,即存储过程实现成功

【注意】:

@@ERROR:当前一个语句遇到错误,则返回错误码(比如除零错误的错误码是:8134),否则返回0。

需要注意的是@@ERROR只对其前一条sql语句有效,在每一条语句执行后会被立刻重置,因此应该在要验证的语句执行后检查数值或者是将它保存到局部变量中以备将来使用。

我们需要查看某个存储过程,则可以使用数据中自带的存储过程查看:

sp_helptext pro_transfer_szmBank

修改现有的存储过程,右键存储过程-->修改:显示的存储过程只是把创建存储过程中的create变为了alter

定义存成过程的时候参数类型默认的输入参数,还有输出参数(out) 和输入输出参数(output)

输出参数和输入输出参数的区别就是:输入参数在执行存储过程的时候是不需要赋值的,输入输出参数在执行存储过程的时候需要赋值

可以在SQL Server的SQL窗口选中某些SQL语句,点击执行,即执行选中的SQL语句

⑤控制台中测试

新建一个控制台项目,在配置文件中添加连接字符串

因为封装的SqlHelper中需要从配置文件中读取数据库连接字符串,所以添加引用:System.Configuration

staticvoidMain(string[] args){//转出账户的Idintfrom=1;//转入账户的Idintto =2;//转账金额decimalbalance =10;    SqlParameter[] param =    {newSqlParameter ("@from",from),newSqlParameter("@to",to),newSqlParameter ("@balance",balance),//-------------------------------注意:这里设置为输出参数newSqlParameter ("@returnNum",System.Data.SqlDbType.Int){Direction=System.Data.ParameterDirection.Output }    };//------------------------设置CommonType为StorProcedure类型SqlHelper.ExecuteNonquery("pro_transfer_szmbank",System.Data.CommandType.StoredProcedure, param);//------------------------获取输出参数//根据输出参数判断转账结果intoutPutparam = Convert.ToInt16(param[3].Value);switch(outPutparam)    {case1: Console.WriteLine($"success:从Id:{from}转账{balance}元到Id:{to}");break;case2: Console.WriteLine("error");break;case3: Console.WriteLine("余额不足");break;    }    Console.ReadKey();}

测试结果:

success:从Id:1转账10元到Id:2

3. 示例2:测试返回DataTable

①存储过程

createprocedure[dbo].[pro_ReturnDataTable]asbeginselectIdas用户ID,Balanceas余额fromszmBank;endgo

②数据库中测试

executepro_ReturnDataTable

测试结果:即显示szmBank中的所有数据

③控制台中测试

staticvoidMain(string[] args){    DataTable dt = SqlHelper.GetDataTable("pro_ReturnDataTable", CommandType.StoredProcedure);foreach(DataRow rowindt.Rows)    {        Console.WriteLine(row["用户ID"].ToString() +":"+ row["余额"].ToString());    }    Console.ReadKey();//TransferAccounts();ReturnDataTable();}

测试结果:即打印szmBank中的所有数据

4. 源代码下载

C#中使用存储过程-源代码下载

所需要的数据库表在示例中已说明,可以直接使用建表语句创建!

存储过程的SQL语句在示例中完整的展示了,可以直接复制!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353