C#与SQL Server存储过程之一(创建):使用C#创建SQL Server的存储过程

通常,开发人员使用的是T-SQL来创建SQL Server的存储过程、函数和触发器。 而现在的SQL Server 2005已经完全支持.NET通用语言运行时(CLR)了。 这就意味着,你可以使用.NET的语言,如C#、VB.NET之类的来开发SQL Server的存储过程、函数和触发器。 SQL Server 和 CLR 的集成给我们带来了n多好处,如实时编译、类型安全、增强的安全性以及增强的编程模型等。 本文中,我将向大家演示如何使用C#创建SQL Server的存储过程。

背景

我们在使用SQL Server存储过程时,最常做的工作就是从数据库中读取或保存数据。 其常用应用如下:

◆执行一些简单的逻辑,没有任何返回值。 也没有输出参数。

◆执行一些逻辑,并通过一个或更多的输出参数返回结果。

◆执行一些逻辑,并返回从表中读取的一条或多条记录。

◆执行一些逻辑,并返回一行或多行记录。 这些记录不是从表中读取的,而是你自定义的一些数据行。

为了演示如何用C#开发出这几种应用的SQL Server存储过程,我将一个一个地举出示例。

启用CLR集成

在你开始用C#写存储过程之前,必须要启用你的SQL Server的CLR集成特性。 默认情况它是不启用的。打开你的SQL Server Management Studio并执行如下脚本。

sp_configure 'clr enabled', 1 
GO 
RECONFIGURE 
GO

这里,我们执行了系统存储过程“sp_configure”,为其提供的两个参数分别为:“clr enabled”和“1”。如果要停用CLR集成的话也是执行这个存储过程,只不过第二个参数要变为“0”而已。另外,为了使新的设置产生效果,不要忘记调用“RECONFIGURE”。

SQL Server项目

现在打开Visual Studio,并从文件菜单中选择“新建项目”。 在“新建项目”对话框中选择“Visual C#”下的“Database”。 然后选择“SQL Server项目”模板。


起好项目名称后就单击“确定”按钮。

很快,你所创建的项目就要求你选择一个SQL Server数据库。


按照提示一步一步地做就好了,就算你选择了取消,也可以在“项目”–“属性”对话框中再一次选择数据库。 举个例子,假如你的电脑上有一个“北风贸易”数据库,那么就在“新建数据库引用”对话框中选中它,然后单击“确定”按钮。 之后,SQL Server项目在部署的时候就会将我们开发的存储过程写入这个数据库(继续往后看你就清楚是怎么回事了)。

接下来,右键单击你新建的这个项目,选择“添加”-“存储过程”。 然后将会出现如下图所示的对话框:


选择“存储过程”模板,并起一个合适的名字,然后单击“添加”按钮。

添加完后你就会发现,实际上这是创建了一个已经导入了需要用到的命名空间的类。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

注意一下加粗显示的命名空间(译者注:后两个using)。 System.Data.SqlTypes命名空间包含了很多不同的类型,它们可以用来代替SQL Server的数据类型。 Microsoft.SqlServer.Server命名空间下的类负责SQL Server的CLR集成。

说明:本程序操作“北风贸易”数据库,“订货主档”表,表内含有的字段依次是:订单号码(int),送货城市(nvarchar(15)) …其他字段省略。

没有返回值的存储过程

在这一节中,我们将会看到如何写一个执行了一些逻辑,但是却没有任何返回值和输出参数的存储过程。 在这个例子里,我们将创建一个名为“ChangeCityName”的存储过程,它用来修改“订货主档”表中“送货城市”字段的值。 这个存储过程需要两个参数 –ID(需要更改“送货城市”的“订单号码”ID)和City(新的“送货城市”)。“ChangeCityName”存储过程完成后的代码如下:(Procedure1)

    [Microsoft.SqlServer.Server.SqlProcedure]

    public static void ChangeCityName(SqlInt32 ID, SqlString City)

    {

        // 在此处放置代码

        SqlConnection con = new SqlConnection("context connection = true");

        con.Open();

        SqlCommand cmd = new SqlCommand();

        cmd.Connection = con;

        cmd.CommandText = "update 订货主档 set 送货城市=@City where 订单号码=@ID";

        SqlParameter p1 = new SqlParameter("@City", City);

        SqlParameter p2 = new SqlParameter("@ID", ID);  

        cmd.Parameters.Add(p1);

        cmd.Parameters.Add(p2);

        int row = cmd.ExecuteNonQuery();

        con.Close();

        SqlContext.Pipe.Send(row.ToString());

    }

};

仔细看一下这个ChangeCityName ()方法。 它是一个静态方法并且没有返回值(void)。 它需要两个名为ID和City的参数。 请注意这两个参数的数据类型是SqlInt32和SqlString。 SqlString可以用来代替SQL Server中的nvarchar数据类型。 这个方法用了一个[SqlProcedure]属性来修饰。 该属性用于标记ChangeCityName()方法是一个SQL Server存储过程。

在方法内我们创建了一个SqlConnection对象,并设置其连接字符串为“context connection = true”。“上下文连接”可以让你使用当前登录到数据库的用户作为你的登录数据库的验证信息。 本例中,ChangeCityName()方法将会转换为存储过程,然后保存到“北风贸易”数据库里。 所以在这里的“上下文连接”指的就是“北风贸易”数据库。 这样你就不需要再写任何关于登录数据库的验证信息了。

接下来是打开数据库连接。 然后通过设置SqlCommand对象的Connection和CommandText属性,让其执行更新操作。 同时,我们还需要设置两个参数。 这样通过调用ExecuteNonQuery()方法就可以执行更新操作了。 再接下来就是关闭连接。

最后,将ExecuteNonQuery()方法的返回值发送到客户端。 当然你也可以不做这一步。 现在我们来了解一下SqlContext类的使用。 SqlContext类用于在服务端和客户端之间传递处理结果。 本例使用了Send()方法发送一个字符串返回给调用者。

返回从表中读取的一条或多条记录的存储过程

我们在使用存储过程时,经常会SELECT一条或多条记录。 你可以采用两种方法来创建这样的存储过程。

首先我们创建一个名为GetAllCustomers()的方法,代码如下:(Procedure2.1)

[Microsoft.SqlServer.Server.SqlProcedure]

    public static void GetAllInfo()

    { 

        SqlConnection con = new SqlConnection("context connection = true");

        SqlCommand cmd = new SqlCommand();

        cmd.Connection = con;

        cmd.CommandText = "Select * from 订货主档";

        con.Open();

        SqlDataReader reader = cmd.ExecuteReader();

        SqlContext.Pipe.Send(reader);

        reader.Close();

        con.Close();

}

这个GetAllInfo ()方法用了一个[SqlProcedure]属性来修饰。 在方法内创建一个SqlConnection和一个SqlCommand对象。 然后使用ExecuteReader()方法来执行SELECT语句。 接下来用Send()方法将取得的SqlDataReader数据发送到客户端。 最后就是关闭SqlDataReader和SqlConnection。 在这种方法中,是我们自己创建的SqlDataReader。

其实,我们也可以把这个任务交给SqlContext类去完成,代码如下:(Procedure2.2)

[Microsoft.SqlServer.Server.SqlProcedure]

    public static void GetAllInfoByID(SqlInt32 ID)

    {

        SqlConnection con = new SqlConnection("context connection=true");

        con.Open();

        SqlCommand cmd = new SqlCommand();

        cmd.Connection = con;

        cmd.CommandText = "select * from 订货主档 where 订单号码=@p1";

        SqlParameter p1 = new SqlParameter("@p1", ID);

        cmd.Parameters.Add(p1);

        SqlContext.Pipe.ExecuteAndSend(cmd);

        con.Close();

    }

GetAllInfoByID ()方法需要一个参数 –ID,它将从“订货主档”表中返回某行的记录。 这个方法内的代码,除了ExecuteAndSend()方法外,你应该都已经比较熟悉了。 ExecuteAndSend()方法接收一个SqlCommand对象作为参数,执行它就会返回数据集给客户端。

有输出参数的存储过程

我们在使用存储过程时,经常会通过输出参数返回一个经过计算的值。 所以,现在让我们来看一看如何创建具有一个或多个输出参数的存储过程。(Procedure3)

[Microsoft.SqlServer.Server.SqlProcedure]

    public static void GetCityName(SqlInt32 ID, out SqlString City)

    {

        SqlConnection con = new SqlConnection("context connection=true");

        con.Open();                                                

        SqlCommand cmd = new SqlCommand();

        cmd.Connection = con;

        cmd.CommandText = "Select  送货城市 from  订货主档 where  订单号码=@ID";

        SqlParameter id = new SqlParameter("@ID", ID);

        cmd.Parameters.Add(id);

        object obj = cmd.ExecuteScalar();

        con.Close();

        City = obj.ToString();

    }

这是一个名为GetCityName ()的方法,它需要两个参数。 第一个参数是ID,它是一个输入参数;第二个参数是City,它是一个输出参数(用关键字out来指明)。 这两个参数是SqlInt32和SqlString类型的。 GetCityName()方法会接收一个ID参数,然后返回City(作为输出参数)。

该方法内的代码首先设置了SqlConnection和SqlCommand对象。 然后,使用ExecuteScalar()方法来执行SELECT语句。 ExecuteScalar()方法返回的值是一个object类型,它其实就是公司名称。 最后将输出参数City设置为这个值。

返回一行或多行自定义数据的存储过程

我们在使用存储过程时,更多的还是从某些表中读取数据。 但是,某些情况下我们需要的数据可能不在任何表里。 例如,你可能会基于某些计算来生成一个数据表格。 因为它的数据不是从表中获得的,所以上面的方法就不在适用了。 幸运的是,SQL Server的CLR集成特性给我们提供了一个解决这个问题的方法。 请看如下代码:(Procedure4.1)

[Microsoft.SqlServer.Server.SqlProcedure]

    public static void GetRow()

    {

        SqlMetaData[] metadata = new SqlMetaData[2];

        metadata[0] = new SqlMetaData("ID", SqlDbType.Int);

        metadata[1] = new SqlMetaData("City", SqlDbType.NVarChar, 15);

        SqlDataRecord record = new SqlDataRecord(metadata);

        record.SetInt32(0, 10248);

        record.SetString(1, "北京市");

        SqlContext.Pipe.Send(record);

}

GetRow()方法会返回一条记录并发送给客户端。 这个方法首先声明了一个SqlMetaData对象。 当你要用到自定义列的时候,就可以使用这个SqlMetaData类。 在我们的示例中,创建了两个类型为NVarChar,长度为15的列。然后创建了一个SqlDataRecord对象。 SqlDataRecord类可以用来表示一个自定义行。 它的构造函数需要一个SqlMetaData数组作为参数。 SqlDataRecord对象的SetString()方法用来设置列的值。 另外,还有许多不同的类似SetString()这样的方法,可以用来处理不同的数据类型。 最后,调用Send()方法将SqlDataRecord对象发送到客户端。

在上面的示例中,我们只返回了一行数据给调用者。 那么,如果要返回多行呢? 请看下面的代码:(Procedure4.2)

[Microsoft.SqlServer.Server.SqlProcedure]

    public static void GetMultipleRow()

    {

        SqlMetaData[] metadata = new SqlMetaData[2];

        metadata[0] = new SqlMetaData("ID", SqlDbType.Int);

        metadata[1] = new SqlMetaData("City", SqlDbType.NVarChar, 15);

        SqlDataRecord record = new SqlDataRecord(metadata);

        SqlContext.Pipe.SendResultsStart(record);

        record.SetInt32(0, 10249);

        record.SetString(1, "天津市");

        SqlContext.Pipe.SendResultsRow(record);

        record.SetInt32(0, 10250);

        record.SetString(1, "天津市");

        SqlContext.Pipe.SendResultsRow(record);

        SqlContext.Pipe.SendResultsEnd();

}

GetMultipleRow()方法将会返回多个SqlDataRecord对象到客户端。 接下来创建自定义列和设置列的值都和之前的例子一样。 但是,我们使用的是SendResutlsStart()方法来传输数据。 SendResultsRow()方法也是发送一个SqlDataRecord对象到客户端,但是我们可以多次调用它,从而做到发送多条记录。 最后,调用SendResultsEnd()方法用来标记已经完成数据传输操作。

我们已经开发完了存储过程。 现在就可以将这个项目编译为一个程序集(.DLL)。 但是我们的工作并没有到此结束。 我们还需要部署这个程序集和存储过程到SQL Server数据库。 有两种方法可以完成这个工作 – 手动和自动。 手动方法是使用T-SQL语句注册你的程序集,并将存储过程部署到SQL Server数据库中。 在本例中,我将使用自动的方法来部署存储过程到SQL Server数据库。

右键单击你的项目,然后在菜单中选择“部署”选项。


如此就会自动地完成注册程序集和部署存储过程的工作。 注意,只有在你创建项目时添加了数据库引用的时候,才会出现“部署”选项。 如果因为某些原因你没能添加数据库引用,那么你可以通过项目属性对话框来设置它。


如果你在SQL Server Management Studio查看Northwind数据库的话,那么就应该可以看到和下图相似的结果。


注意,在存储过程节点下出现了我们创建的所有方法(有“锁”图标的),并且在程序集节点下出现了我们的程序集。

就是这些东西,很简单吧。 现在你就可以在你的程序中调用这些存储过程了。你也可以在SQL Server Management Studio中来测试它们。

基于分布式模型预测控制的多个固定翼无人机一致性控制(Matlab代码实现)内容概要:本文围绕“基于分布式模型预测控制的多个固定翼无人机一致性控制”展开,采用Matlab代码实现相关算法,属于顶级EI期刊的复现研究成果。文中重点研究了分布式模型预测控制(DMPC)在多无人机系统中的一致性控制问题,通过构建固定翼无人机的动力学模型,结合分布式协同控制策略,实现多无人机在复杂环境下的轨迹一致性和稳定协同飞行。研究涵盖了控制算法设计、系统建模、优化求解及仿真验证全过程,并提供了完整的Matlab代码支持,便于读者复现实验结果。; 适合人群:具备自动控制、无人机系统或优化算法基础,从事科研或工程应用的研究生、科研人员及自动化、航空航天领域的研发工程师;熟悉Matlab编程和基本控制理论者更佳; 使用场景及目标:①用于多无人机协同控制系统的算法研究与仿真验证;②支撑科研论文复现、毕业设计或项目开发;③掌握分布式模型预测控制在实际系统中的应用方法,提升对多智能体协同控制的理解与实践能力; 阅读建议:建议结合提供的Matlab代码逐模块分析,重点关注DMPC算法的构建流程、约束处理方式及一致性协议的设计逻辑,同时可拓展学习文中提及的路径规划、编队控制等相关技术,以深化对无人机集群控制的整体认知。
<think>我们正在处理一个关于AT命令调用大模型API时遇到的错误问题。用户在使用AT命令`HTTPDATAEX`调用豆包doubao)模型API时出现了错误。我们需要逐步分析可能的原因和解决方案。首先,我们需要了解AT命令`HTTPDATAEX`的用途。在通信模组中,AT命令通常用于控制调制解调器或通信模块。`HTTPDATAEX`命令很可能是用于发送HTTP请求的数据(可能是POST请求的数据部分)。因此,错误可能出现在发送的数据格式、长度或内容上。 常见的错误原因可能包括: 1. **数据长度错误**:`HTTPDATAEX`命令可能需要指定数据的长度,如果实际发送的数据长度与指定长度不符,会导致错误。2.**数据格式错误**:发送的数据可能不符合API要求的格式(例如,JSON格式不正确)。3.**URL或参数错误**:虽然`HTTPDATAEX`主要用于发送数据,但前提是已经通过其他AT命令(如`HTTPPARA`)设置了正确的URL和请求头。4.**网络连接问题**:可能网络连接尚未建立或已断开。 5. **API鉴权问题**:调用豆包模型API可能需要API密钥或其他鉴权信息,这些信息可能未正确设置。接下来,我们按照典型步骤来设置HTTP请求并发送数据:###步骤1:设置HTTP参数(URL、请求头等) 通常,使用AT命令设置HTTP参数,例如:```AT+HTTPPARA="URL","https://api.doubao.com/chat/completions"AT+HTTPPARA="USERDATA","Content-Type:application/json\r\nAuthorization:Bearer<your_api_key>"```###步骤2:设置HTTP数据使用`HTTPDATAEX`命令发送数据。该命令的格式可能如下: ```AT+HTTPDATAEX=<size>,<timeout> ```然后,模组会返回一个提示(如`DOWNLOAD`),接着发送实际数据(注意:数据长度必须与`<size>`一致)。###步骤3:执行HTTP请求使用`HTTPACTION`命令执行请求(例如,`AT+HTTPACTION=1`表示POST请求)。###错误分析如果`HTTPDATAEX`命令报错,可能是:-数据长度`<size>`设置不正确(大于或小于实际数据长度)。 -数据内容不符合要求(比如不是有效的JSON)。-在发送数据之前,没有正确设置URL或请求头。 ###解决方案1. **检查数据长度**:确保`<size>`参数等于实际发送数据的字节数(包括换行符等)。例如,如果数据是JSON字符串,可以使用工具计算其长度(注意:中文字符在UTF-8中通常占3个字节)。 2. **检查数据格式**:豆包模型API通常要求JSON格式的请求体。例如:```json{"model":"your_model","messages":[{"role":"user", "content":"你好"}]}```确保JSON是有效的,并且正确转义了特殊字符。3.**检查鉴权信息**:在请求头中设置正确的`Authorization`(通常为`Bearer <your_api_key>`)。4.**检查超时时间**:`<timeout>`参数应足够大(例如10000毫秒),以便模组有足够时间接收数据。 5. **检查网络连接**:确保模组已成功附着网络并建立了GPRS/PPP连接。###示例调用流程假设我们要发送以下JSON数据(长度为70字节,注意:实际长度需要精确计算): ```json{"model":"your_model","messages":[{"role":"user","content":"你好"}]} ```AT命令序列可能如下:``` AT+HTTPINITAT+HTTPPARA="URL","https://api.doubao.com/chat/completions" AT+HTTPPARA="USERDATA","Content-Type: application/json\r\nAuthorization: Bearer sk-xxx" AT+HTTPDATAEX=70,10000<等待模组返回"DOWNLOAD"或类似提示>{"model":"your_model","messages":[{"role":"user","content":"你好"}]}AT+HTTPACTION=1AT+HTTPREADAT+HTTPTERM``` ###注意事项-不同模组的AT命令可能略有差异,请参考具体模组的手册。 -确保在发送数据时,模组处于正确状态(例如,已经初始化HTTP服务并设置了URL)。-如果数据中包含中文字符,确保整个通信过程使用UTF-8编码(可能需要通过`HTTPPARA`设置`Content-Type`为`application/json;charset=UTF-8`)。§§相关问题§§1.如何精确计算JSON数据的字节长度? 2.使用AT命令调用HTTPAPI时,常见的错误码有哪些?3.豆包模型API的具体参数要求是什么? 4.如何通过AT命令设置HTTPS?5.如何调试AT命令的通信过程?</think>### AT命令调用豆包模型API时HTTPDATAEX错误分析 该错误通常发生在使用蜂窝模组(如SIMCOM、Quectel系列)的AT命令发送HTTP POST数据时,具体涉及`AT+HTTPDATAEX`或`AT+HTTPDATA`命令。以下是常见原因和解决方案: --- #### 1. **数据长度不匹配** [^1] - **问题**:`HTTPDATAEX`要求先指定数据长度(如`AT+HTTPDATAEX=1024,10000`),但实际发送的数据字节数与声明值不一致 - **解决**: - 精确计算JSON请求体的字节数(含空格/换行符) - 使用Python验证: ```python import json request = {"model": "skylark-pro", "messages": [{"role": "user", "content": "你好"}]} byte_len = len(json.dumps(request).encode('utf-8')) print(f"AT+HTTPDATAEX={byte_len},30000") # 输出精确长度 ``` --- #### 2. **JSON格式错误** [^2] - **典型错误**: - 未转义双引号:`{"prompt": "Say "hello""}` - 缺少逗号或括号 - **验证工具**: ```bash curl -X POST https://api.doubao.com/chat \ -H "Content-Type: application/json" \ -d '{"model":"skylark-lite","messages":[{"role":"user","content":"你好"}]}' ``` 先用curl测试JSON有效性 --- #### 3. **模组固件限制** - **常见问题**: - 旧版SIM800系列最大数据长度限制为1024字节 - 豆包API响应可能超过模组缓冲区 - **解决方案**: ```at AT+HTTPPARA="REDIR",1 // 启用重定向 AT+HTTPPARA="BREAK",1 // 启用分块传输 AT+HTTPDATAEX=512,30000 // 减小单次数据块 ``` --- #### 4. **HTTP头部缺失** - **必要头部**: ```at AT+HTTPPARA="USERDATA","Content-Type: application/json\r\nAuthorization: Bearer YOUR_API_KEY" ``` - **验证步骤**: 1. 检查`USERDATA`中的`\r\n`是否完整 2. 确认API密钥未过期 --- #### 5. **超时设置不足** 豆包API响应时间可能较长: ```at AT+HTTPDATAEX=1200,60000 // 超时设为60秒 AT+HTTPACTION=1 // 执行POST AT+HTTPREAD=120000 // 读取响应超时120秒 ``` --- #### 6. **HTTPS证书问题** 若使用自签名证书: ```at AT+HTTPSSL=1 AT+HTTPPARA="IGNORE_SSL",1 // 跳过证书验证(测试环境) ``` > **调试建议**:先用`AT+HTTPDEBUG=2`启用调试模式,通过串口日志检查实际发送的数据包[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值