执行 CLR SQL 函数时出现问题
Trouble Executing a CLR SQL Function
我创建了一个简单的 1 方法 DLL,使用内部 Web 服务将字符串地址转换为 Lat/Lon 字符串。代码很简单:
public class GeoCodeLib
{
[SqlFunction(IsDeterministic=false)]
public SqlString GetLatLon(SqlString addr)
{
Geo.GeoCode gc = new Geo.GeoCode();
Geo.Location loc = gc.GetLocation(addr.Value);
return new SqlString(string.Format("{0:N6}, {1:N6}", loc.LatLon.Latitude, loc.LatLon.Longitude));
}
}
我的目标是 .NET Framework 2.0。我们正在使用 SQL Server 2008 R2。该程序集已添加到数据库中(该程序集名为 GeoCodeSqlLib.dll)。我们没有对其设置任何权限。
我好像不能调用它。我试过:
select GeoCodeSqlLib.GeoCodeLib.GetLatLon('12143 Thompson Dr. Fayetteville, AR 72704')
这给了我消息,Cannot find either column "GeoCodeSqlLib" or the user-defined function or aggregate "GeoCodeSqlLib.GeoCodeLib.GetLatLon", or the name is ambiguous.
我试过:
CREATE FUNCTION GetLatLon(@amount varchar(max)) RETURNS varchar(max)
AS EXTERNAL NAME GeoCodeSqlLib.GeoCodeLib.GetLatLon
这给了我消息,Could not find Type 'GeoCodeLib' in assembly 'GeoCodeSqlLib'.
我显然遗漏了什么。文档的示例都是非常基本的,无论我遗漏了什么步骤,我都无法在文档中找到它。
更新
感谢大家的帮助。所以有几件事:
- 我需要将其设置为外部访问才能进行 Web 服务调用。我没有意识到我需要创建一个 SQL 服务器项目。我只是在做一个常规的 C# class 库。
- 所以我创建了 SQL 服务器项目。 SQL 服务器项目不支持 Web 服务引用。我尝试添加对原始 DLL 的引用,因此新的 SQL 服务器项目 DLL 充当原始 DLL 的代理(我原始问题中的代码)。
- 然而,这给了我:
Assembly 'geocodesqllib, version ...' was not found in the
SQL catalog.
但是我不能将它添加到目录中,因为它需要
外部访问,我无法从非 SQL 服务器项目(即
我知道)。
他们真的不想让它变得简单,是吗?
更新 2
根据我在下面的评论,最后一个问题似乎是配置文件。它没有找到设置。服务器名称已更改以保护无辜者:
<applicationSettings>
<GeoCodeSqlLib.Properties.Settings>
<setting name="GeoCodeSqlLib_ourserver_GeoCode" serializeAs="String">
<value>http://ourserver/GeoCode.svc</value>
</setting>
</GeoCodeSqlLib.Properties.Settings>
</applicationSettings>
我得到的错误是:
System.Configuration.SettingsPropertyNotFoundException: The settings property 'GeoCodeSqlLib_ourserver_GeoCode' was not found.
一些事情:
正如 Jeroen Mostert 在对该问题的评论中提到的,SQLCLR 方法必须声明为 static
。
在您的 CREATE FUNCTION
语句中,您需要使用 NVARCHAR
而不是 VARCHAR
。 SQLCLR 不支持 VARCHAR
.
在您的CREATE FUNCTION
语句中,输入参数和return类型不需要使用MAX
。纬度和经度组合总是少于 4000 个字符,我很确定地址也将少于 4000 个字符。即使签名中只有一个 MAX
数据类型,也会对性能产生一定的影响。你最好让两者都成为 NVARCHAR(4000)
.
如果您仍然收到 "cannot find type" 错误,那么您很可能还有一个名称空间未在问题的代码中显示。 EXTERNAL NAME
语法是:
EXTERNAL NAME AssemblyName.[NamespaceName.ClassName].MethodName;
所以最终应该看起来像这样:
CREATE FUNCTION dbo.GetLatLon(@Address NVARCHAR(4000))
RETURNS NVARCHAR(4000)
AS EXTERNAL NAME GeoCodeSqlLib.[{namespace_name}.GeoCodeLib].GetLatLon;
关于此声明:
I have targeted .NET Framework 2.0. We're using SQL Server 2008 R2
您可以使用 .NET Framework 版本 3.5 作为 CLR 2.0 中的 3.0 和 3.5 运行,以及 SQL Server 2005、2008 和2008 R2 链接到 CLR 2.0。
根据问题中的 UPDATE 部分:
不,您 不需要 将 Visual Studio 项目设为 "Database Project",但它确实使它的发布更容易一些。
为了将 GeoCodeSqlLib
库设置为 EXTERNAL_ACCESS
,您可以通过 Visual Studio 中数据库项目的项目属性来实现。您还可以将 WITH PERMISSION_SET = EXTERNAL_ACCESS
添加到 CREATE ASSEMBLY
语句,这是 Visual Studio 发布过程(由 SSDT 处理)无论如何都在做的事情。
现在,为了能够将任何程序集设置为 EXTERNAL_ACCESS
(甚至 UNSAFE
),您需要执行以下操作:
签署程序集(也可以在 Visual Studio 项目属性中轻松完成)并确保 "protect the key file with a password".
构建项目(不发布)以便创建 DLL。
在 master
数据库中,从该 DLL 创建一个非对称密钥。
仍在 master
,从该非对称密钥创建登录。
授予新的基于密钥的登录 EXTERNAL ACCESS ASSEMBLY
权限。
现在您可以使用 WITH PERMISSION_SET = EXTERNAL_ACCESS
(在 CREATE ASSEMBLY
或 ALTER ASSEMBLY
语句中对任何使用该特定密钥签名的程序集)而不会出现错误。
这可能有助于更清楚地了解如何首先使用 SQLCLR。请参阅我在 SQL Server Central 上就此主题撰写的系列文章(需要免费注册才能阅读该站点上的内容):
该系列中的第 7 级甚至描述了一种使用 Visual Studio / SSDT 来管理上述步骤以使其以自动化方式工作的方法,因为 VS / SSDT 不处理这方面的安全性(这很可悲,因为它鼓励人们走简单的路线,将数据库设置为 TRUSTWORTHY ON
,这是一个安全漏洞)。我的下一篇文章将描述一种更简单的方法,在 Visual Studio.
中使用 T-4 模板
对于 更新 2 部分和对此答案的附加评论:
由于每个数据库/所有者组合都有一个应用程序域,因此在特定程序集中执行代码的所有会话都将共享该确切的代码和内存。如果您有一个静态 class 变量(即在应用程序域的生命周期内保留其值的变量),那么它会在所有会话之间共享,这可能会导致意外/不可靠的行为。因此,使用静态 class 变量需要将程序集标记为 UNSAFE
。否则,如果变量标记为 readonly
.
,则可以在 SAFE
和 EXTERNAL_ACCESS
程序集中使用静态 class 变量
关于应用程序配置文件,你可以使用它们,但"app"在这种情况下是SQL服务器。因此,您始终可以将配置详细信息放在 machine.config 中,这对特定 CLR 版本(在您的情况下为 CLR 2.0)的所有进程都是全局的,或者您可以创建一个配置SQL 服务器的文件,如我的这个答案所示,也在 S.O 上显示:
我创建了一个简单的 1 方法 DLL,使用内部 Web 服务将字符串地址转换为 Lat/Lon 字符串。代码很简单:
public class GeoCodeLib
{
[SqlFunction(IsDeterministic=false)]
public SqlString GetLatLon(SqlString addr)
{
Geo.GeoCode gc = new Geo.GeoCode();
Geo.Location loc = gc.GetLocation(addr.Value);
return new SqlString(string.Format("{0:N6}, {1:N6}", loc.LatLon.Latitude, loc.LatLon.Longitude));
}
}
我的目标是 .NET Framework 2.0。我们正在使用 SQL Server 2008 R2。该程序集已添加到数据库中(该程序集名为 GeoCodeSqlLib.dll)。我们没有对其设置任何权限。
我好像不能调用它。我试过:
select GeoCodeSqlLib.GeoCodeLib.GetLatLon('12143 Thompson Dr. Fayetteville, AR 72704')
这给了我消息,Cannot find either column "GeoCodeSqlLib" or the user-defined function or aggregate "GeoCodeSqlLib.GeoCodeLib.GetLatLon", or the name is ambiguous.
我试过:
CREATE FUNCTION GetLatLon(@amount varchar(max)) RETURNS varchar(max)
AS EXTERNAL NAME GeoCodeSqlLib.GeoCodeLib.GetLatLon
这给了我消息,Could not find Type 'GeoCodeLib' in assembly 'GeoCodeSqlLib'.
我显然遗漏了什么。文档的示例都是非常基本的,无论我遗漏了什么步骤,我都无法在文档中找到它。
更新
感谢大家的帮助。所以有几件事:
- 我需要将其设置为外部访问才能进行 Web 服务调用。我没有意识到我需要创建一个 SQL 服务器项目。我只是在做一个常规的 C# class 库。
- 所以我创建了 SQL 服务器项目。 SQL 服务器项目不支持 Web 服务引用。我尝试添加对原始 DLL 的引用,因此新的 SQL 服务器项目 DLL 充当原始 DLL 的代理(我原始问题中的代码)。
- 然而,这给了我:
Assembly 'geocodesqllib, version ...' was not found in the SQL catalog.
但是我不能将它添加到目录中,因为它需要 外部访问,我无法从非 SQL 服务器项目(即 我知道)。
他们真的不想让它变得简单,是吗?
更新 2
根据我在下面的评论,最后一个问题似乎是配置文件。它没有找到设置。服务器名称已更改以保护无辜者:
<applicationSettings>
<GeoCodeSqlLib.Properties.Settings>
<setting name="GeoCodeSqlLib_ourserver_GeoCode" serializeAs="String">
<value>http://ourserver/GeoCode.svc</value>
</setting>
</GeoCodeSqlLib.Properties.Settings>
</applicationSettings>
我得到的错误是:
System.Configuration.SettingsPropertyNotFoundException: The settings property 'GeoCodeSqlLib_ourserver_GeoCode' was not found.
一些事情:
正如 Jeroen Mostert 在对该问题的评论中提到的,SQLCLR 方法必须声明为
static
。在您的
CREATE FUNCTION
语句中,您需要使用NVARCHAR
而不是VARCHAR
。 SQLCLR 不支持VARCHAR
.在您的
CREATE FUNCTION
语句中,输入参数和return类型不需要使用MAX
。纬度和经度组合总是少于 4000 个字符,我很确定地址也将少于 4000 个字符。即使签名中只有一个MAX
数据类型,也会对性能产生一定的影响。你最好让两者都成为NVARCHAR(4000)
.如果您仍然收到 "cannot find type" 错误,那么您很可能还有一个名称空间未在问题的代码中显示。
EXTERNAL NAME
语法是:EXTERNAL NAME AssemblyName.[NamespaceName.ClassName].MethodName;
所以最终应该看起来像这样:
CREATE FUNCTION dbo.GetLatLon(@Address NVARCHAR(4000)) RETURNS NVARCHAR(4000) AS EXTERNAL NAME GeoCodeSqlLib.[{namespace_name}.GeoCodeLib].GetLatLon;
关于此声明:
I have targeted .NET Framework 2.0. We're using SQL Server 2008 R2
您可以使用 .NET Framework 版本 3.5 作为 CLR 2.0 中的 3.0 和 3.5 运行,以及 SQL Server 2005、2008 和2008 R2 链接到 CLR 2.0。
根据问题中的 UPDATE 部分:
不,您 不需要 将 Visual Studio 项目设为 "Database Project",但它确实使它的发布更容易一些。
为了将 GeoCodeSqlLib
库设置为 EXTERNAL_ACCESS
,您可以通过 Visual Studio 中数据库项目的项目属性来实现。您还可以将 WITH PERMISSION_SET = EXTERNAL_ACCESS
添加到 CREATE ASSEMBLY
语句,这是 Visual Studio 发布过程(由 SSDT 处理)无论如何都在做的事情。
现在,为了能够将任何程序集设置为 EXTERNAL_ACCESS
(甚至 UNSAFE
),您需要执行以下操作:
签署程序集(也可以在 Visual Studio 项目属性中轻松完成)并确保 "protect the key file with a password".
构建项目(不发布)以便创建 DLL。
在
master
数据库中,从该 DLL 创建一个非对称密钥。仍在
master
,从该非对称密钥创建登录。授予新的基于密钥的登录
EXTERNAL ACCESS ASSEMBLY
权限。
现在您可以使用 WITH PERMISSION_SET = EXTERNAL_ACCESS
(在 CREATE ASSEMBLY
或 ALTER ASSEMBLY
语句中对任何使用该特定密钥签名的程序集)而不会出现错误。
这可能有助于更清楚地了解如何首先使用 SQLCLR。请参阅我在 SQL Server Central 上就此主题撰写的系列文章(需要免费注册才能阅读该站点上的内容):
该系列中的第 7 级甚至描述了一种使用 Visual Studio / SSDT 来管理上述步骤以使其以自动化方式工作的方法,因为 VS / SSDT 不处理这方面的安全性(这很可悲,因为它鼓励人们走简单的路线,将数据库设置为 TRUSTWORTHY ON
,这是一个安全漏洞)。我的下一篇文章将描述一种更简单的方法,在 Visual Studio.
对于 更新 2 部分和对此答案的附加评论:
由于每个数据库/所有者组合都有一个应用程序域,因此在特定程序集中执行代码的所有会话都将共享该确切的代码和内存。如果您有一个静态 class 变量(即在应用程序域的生命周期内保留其值的变量),那么它会在所有会话之间共享,这可能会导致意外/不可靠的行为。因此,使用静态 class 变量需要将程序集标记为
UNSAFE
。否则,如果变量标记为readonly
. ,则可以在 关于应用程序配置文件,你可以使用它们,但"app"在这种情况下是SQL服务器。因此,您始终可以将配置详细信息放在 machine.config 中,这对特定 CLR 版本(在您的情况下为 CLR 2.0)的所有进程都是全局的,或者您可以创建一个配置SQL 服务器的文件,如我的这个答案所示,也在 S.O 上显示:
SAFE
和 EXTERNAL_ACCESS
程序集中使用静态 class 变量