如何将参数从 SQL Server 传递到基于 CLR 的存储过程
本文关键字:CLR 存储过程 参数 Server SQL | 更新日期: 2023-09-27 18:18:55
使用这个例子:
http://www.sommarskog.se/dynsearch-2008/search_orders_cs.cs
....我构建并成功地将基于 CLR 的存储过程部署到 SQL Server。 在 SSMS 中查看生成的存储过程定义(请参阅下面的代码(时,我注意到 C# 定义中定义的参数确实是 SP 定义的一部分,但是当您查看对存储过程的实际调用时,没有对参数的引用,所以我很好奇参数值实际上是如何传递的?
(我特别问的原因是,在 c# 代码中,我想知道我是否可以将 Query 变量设置为我想要的任何现有存储过程,然后在一个大的 varchar 参数中将所有相关参数作为分隔的键值对传递,然后拆分它们并将它们添加为循环中的 Command.Parameters。 基本上,我正在尝试构建一个通用存储过程,它可以执行任何其他存储过程,对参数的不同计数或数据类型没有任何限制,所有这些都将在运行时读取。 因此,基本上,从 C# 调用 SQL Server 时具有相同的灵活性,但在 SQL Server 中实现除外。
生成的存储过程(在 SSMS 中查看(:
CREATE PROCEDURE [dbo].[search_orders_cs]
@Orderid [int],
@Fromdate [datetime],
@Todate [datetime],
@Minprice [money],
@Maxprice [money],
@Custid [nvarchar](4000),
@Custname [nvarchar](4000),
@City [nvarchar](4000),
@Region [nvarchar](4000),
@Country [nvarchar](4000),
@Prodid [int],
@Prodname [nvarchar](4000),
@Debug [bit]
WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [SqlServerProject1].[StoredProcedures].[search_orders_cs]
GO
EXEC sys.sp_addextendedproperty @name=N'SqlAssemblyFile', @value=N'twg_clr_based_sp.cs' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'PROCEDURE',@level1name=N'search_orders_cs'
GO
EXEC sys.sp_addextendedproperty @name=N'SqlAssemblyFileLine', @value=N'23' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'PROCEDURE',@level1name=N'search_orders_cs'
GO
C# 源:
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public partial class StoredProcedures
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void search_orders_cs(
SqlInt32 Orderid,
SqlDateTime Fromdate,
SqlDateTime Todate,
SqlMoney Minprice,
SqlMoney Maxprice,
SqlString Custid,
SqlString Custname,
SqlString City,
SqlString Region,
SqlString Country,
SqlInt32 Prodid,
SqlString Prodname,
SqlBoolean Debug)
{
string Query;
SqlCommand Command = new SqlCommand();
Query = @"SELECT o.OrderID, o.OrderDate, od.UnitPrice, od.Quantity,
c.CustomerID, c.CompanyName, c.Address, c.City,
c.Region, c.PostalCode, c.Country, c.Phone,
p.ProductID, p.ProductName, p.UnitsInStock,
p.UnitsOnOrder
FROM dbo.Orders o
JOIN dbo.[Order Details] od ON o.OrderID = od.OrderID
JOIN dbo.Customers c ON o.CustomerID = c.CustomerID
JOIN dbo.Products p ON p.ProductID = od.ProductID
WHERE 1 = 1 ";
if (!Orderid.IsNull)
{
Query += " AND o.OrderID = @orderid " +
" AND od.OrderID = @orderid";
Command.Parameters.Add("@orderid", SqlDbType.Int);
Command.Parameters["@orderid"].Value = Orderid;
Command.Parameters["@orderid"].Direction = ParameterDirection.Input;
}
if (!Fromdate.IsNull)
{
Query += " AND o.OrderDate >= @fromdate";
Command.Parameters.Add("@fromdate", SqlDbType.DateTime);
Command.Parameters["@fromdate"].Value = Fromdate;
Command.Parameters["@fromdate"].Direction = ParameterDirection.Input;
}
if (!Todate.IsNull)
{
Query += " AND o.OrderDate <= @todate";
Command.Parameters.Add("@todate", SqlDbType.DateTime);
Command.Parameters["@todate"].Value = Todate;
Command.Parameters["@todate"].Direction = ParameterDirection.Input;
}
if (!Minprice.IsNull)
{
Query += " AND od.UnitPrice >= @minprice";
Command.Parameters.Add("@minprice", SqlDbType.Money);
Command.Parameters["@minprice"].Value = Minprice;
Command.Parameters["@minprice"].Direction = ParameterDirection.Input;
}
if (!Maxprice.IsNull)
{
Query += " AND od.UnitPrice <= @maxprice";
Command.Parameters.Add("@maxprice", SqlDbType.Money);
Command.Parameters["@maxprice"].Value = Maxprice;
Command.Parameters["@maxprice"].Direction = ParameterDirection.Input;
}
if (!Custid.IsNull)
{
Query += " AND o.CustomerID = @custid" +
" AND c.CustomerID = @custid";
Command.Parameters.Add("@custid", SqlDbType.NChar, 5);
Command.Parameters["@custid"].Value = Custid;
Command.Parameters["@custid"].Direction = ParameterDirection.Input;
}
if (!Custname.IsNull)
{
Query += " AND c.CompanyName LIKE @custname + '%'";
Command.Parameters.Add("@custname", SqlDbType.NVarChar, 40);
Command.Parameters["@custname"].Value = Custname;
Command.Parameters["@custname"].Direction = ParameterDirection.Input;
}
if (!City.IsNull)
{
Query += " AND c.City = @city";
Command.Parameters.Add("@city", SqlDbType.NVarChar, 15);
Command.Parameters["@city"].Value = City;
Command.Parameters["@city"].Direction = ParameterDirection.Input;
}
if (!Region.IsNull)
{
Query += " AND c.Region = @region";
Command.Parameters.Add("@region", SqlDbType.NVarChar, 15);
Command.Parameters["@region"].Value = Region;
Command.Parameters["@region"].Direction = ParameterDirection.Input;
}
if (!Country.IsNull)
{
Query += " AND c.Country = @country";
Command.Parameters.Add("@country", SqlDbType.NVarChar, 15);
Command.Parameters["@country"].Value = Country;
Command.Parameters["@country"].Direction = ParameterDirection.Input;
}
if (!Prodid.IsNull)
{
Query += " AND od.ProductID = @prodid" +
" AND p.ProductID = @prodid";
Command.Parameters.Add("@prodid", SqlDbType.Int);
Command.Parameters["@prodid"].Value = Prodid;
Command.Parameters["@prodid"].Direction = ParameterDirection.Input;
}
if (!Prodname.IsNull)
{
Query += " AND p.ProductName LIKE @prodname + '%'";
Command.Parameters.Add("@prodname", SqlDbType.NVarChar, 40);
Command.Parameters["@prodname"].Value = Prodname;
Command.Parameters["@prodname"].Direction = ParameterDirection.Input;
}
Query += " ORDER BY o.OrderID";
using (SqlConnection Connection = new SqlConnection("context connection=true"))
{
Connection.Open();
if (Debug)
{
SqlContext.Pipe.Send(Query);
}
Command.CommandType = CommandType.Text;
Command.CommandText = Query;
Command.Connection = Connection;
SqlContext.Pipe.ExecuteAndSend(Command);
}
}
};
一个可用于在 DLL 中调用方法的示例。
第 1 步。在 Visual Studio 中创建库类
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
namespace MyNamespace
{
public static class MyClass
{
[SqlProcedure]
public static void MyMethod(SqlString strInParam, out SqlString strOutParam)
{
strOutParam = $"Hi '{strInParam}', The date time is: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
}
}
}
第 2 步。创建程序集
CREATE ASSEMBLY [AssemblyName]
AUTHORIZATION dbo
FROM 'DLL_Path'
WITH PERMISSION_SET = SAFE
GO
第 3 步。创建过程
-- DROP PROCEDURE MyProcedure
CREATE PROCEDURE MyProcedure(@strInParam nvarchar(1000), @strOutParam nvarchar(1000) OUTPUT)
AS EXTERNAL NAME [AssemblyName].[MyNamespace.MyClass].[MyMethod]
GO
第 4 步。启用 CLR
EXEC sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO
EXEC sp_configure 'clr enabled'
GO
第5步。执行存储过程将导致调用 书面方法
DECLARE @res NVARCHAR(1000);
EXEC dbo.MyProcedure @strInParam = 'Siya', @strOutParam = @res OUTPUT;
SELECT @res
结果是:
嗨'Siya', 日期时间是:2018/05/19 14:17:47
它是您在 SQL没有对参数的引用,所以我很好奇参数值实际上是如何传递的?
Server 中看到并通过 T-CREATE PROCEDURE
SQL 交互的 T-SQL 包装器对象(即存储过程、函数、用户定义类型、用户定义聚合或触发器(与通过该 T-SQL 包装器对象的 EXTERNAL NAME
子句引用的 .NET 方法之间的相同签名(仅数据类型和位置(。
T-SQL 包装对象参数与 .NET 方法的参数之间的参数名称不匹配。您可以通过重新排列问题中显示的存储过程中的参数名称来验证这一点,甚至可以将它们全部重命名为 @a
、@b
、'@c'等。然后将CREATE
更改为 ALTER
,运行它,然后使用相同的输入再次运行存储过程,它仍将像参数名称更改之前一样工作。
与此相关的是,SQLCLR的特定用法是毫无意义的。与在纯 T-SQL 中执行相同操作相比,它没有任何好处(即在 3 个 NVARCHAR(MAX( 变量中动态创建查询、参数列表和参数值列表,然后将它们组合到第二个动态创建的查询中,这只是EXEC sp_executesql
调用。现在,如果该动态搜索过程是TVF,那将是SQLCLR的有效用途,因为它将执行在T-SQL中无法完成的事情:函数中的动态SQL!