Msg 6522,执行 clr 存储过程期间的 16 级警告

Msg 6522, Level 16 warning during execution of clr stored procedure

我想创建 SQL Server CLR 存储过程,用于在 SQL Server 2012 的 table 中插入一些行。

这是我的 C# 代码:

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

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void InsertingRows ()
    {
        // Put your code here
        Random rnd = new Random();

        List<int> listtelnumber = new List<int>(new int[] { 1525407, 5423986, 1245398, 32657891, 123658974, 7896534, 12354698 });
        List<string> listfirstname = new List<string>(new string[] { "Babak", "Carolin", "Martin", "Marie", "Susane", "Michail", "Ramona", "Ulf", "Dirk", "Sebastian" });
        List<string> listlastname = new List<string>(new string[] { "Bastan", "Krause", "Rosner", "Gartenmeister", "Rentsch", "Benn", "Kycik", "Leuoth", "Kamkar", "Kolaee" });
        List<string> listadres = new List<string>(new string[] { "Deutschlan Chemnitz Sonnenstraße 59", "",
            "Deutschland Chemnitz Arthur-Strobel straße 124", " Deutschland Chemnitz Brückenstraße 3",
            "Iran Shiraz Chamran Blvd, Niayesh straße Nr.155", "",
            "Deutschland Berlin Charlotenburg Pudbulesky Alleee 52", "United State of America Washington DC. Farbod Alle",
            "" });

            using (SqlConnection conn = new SqlConnection("Data Source=WIN2012SERVER02;Initial Catalog=test;Persist Security Info=True;User ID=di_test;Password=di_test"))
            {
                SqlCommand insertcommand = new SqlCommand();
                SqlParameter firstname = new SqlParameter("@fname", SqlDbType.VarChar);
                SqlParameter lastname = new SqlParameter("@lname", SqlDbType.VarChar);
                SqlParameter tel = new SqlParameter("@tel", SqlDbType.Int);
                SqlParameter adres = new SqlParameter("@adres", SqlDbType.NVarChar);
                conn.Open();
            for (int i = 0; i < 10000; i++)
            {
                int tn = rnd.Next(0, 6);
                int fn = rnd.Next(0, 9);
                int ln = rnd.Next(0, 9);
                int an = rnd.Next(0, 9);

                firstname.Value = listfirstname[fn];
                lastname.Value = listlastname[ln];
                tel.Value = listtelnumber[tn];
                adres.Value = listadres[an];

                insertcommand.Parameters.Add(firstname);
                insertcommand.Parameters.Add(lastname);
                insertcommand.Parameters.Add(tel);
                insertcommand.Parameters.Add(adres);

                insertcommand.CommandText = "INSERT dbo.Unsprstb(Firstname,Lastname,Tel,adress) VALUES(@fname,@lname,@tel,@adres)";
                insertcommand.Connection = conn;

                insertcommand.ExecuteNonQuery();

            }
            conn.Close();
        }
    }
}

我可以在 SQL 服务器中成功构建、部署和发布我的代码,但是如果我在 SQL 服务器中 运行 这个 CLR 存储过程,我会看到这条消息:

Msg 6522, Level 16, State 1, procedure InsertingRows,Line 0
A .NET Framework error occurred during execution of user defined routine or aggregate 'InsertingRows':
System.Security.SecurityException: Error request for the permission of type "System.Data.SqlClient.SqlClientPermission, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089".
System.Security.SecurityException:
bei System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
bei System.Security.PermissionSet.Demand()
bei System.Data.Common.DbConnectionOptions.DemandPermission()
bei System.Data.SqlClient.SqlConnectionFactory.PermissionDemand(DbConnection outerConnection)
bei System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource1 retry, DbConnectionOptions userOptions)<br> bei System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource1 retry)
bei System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
bei System.Data.SqlClient.SqlConnection.Open()
bei StoredProcedures.InsertingRows()

我该如何解决这个问题?

在您的 SQL CLR C# 代码中,您应该 而不是 建立与显式服务器、数据库名称和凭据的连接 - 相反,使用 "context"连接:

using (SqlConnection conn = new SqlConnection("context connection=true"))
{
    // do your stuff here...
}    

这段代码中有几个问题需要解决:

  1. 关于上述问题,当您收到 System.Security.SecurityException 错误时,该错误指的是试图到达数据库外部的代码,有些东西这在 SAFE 程序集中是不允许的。如何解决此问题取决于您要完成的任务。

    • 如果您尝试访问文件系统,请从注册表中读取,获取环境变量,访问非SQL服务器连接的网络(例如http,ftp),等等,然后程序集需要 EXTERNAL_ACCESSPERMISSION_SET。为了将程序集设置为 SAFE 以外的任何值,您需要:
      • 根据您用于签署程序集的相同密钥创建证书或非对称密钥(即给它一个强名称),根据该证书或非对称密钥创建登录,然后授予 EXTERNAL ACCESS ASSEMBLY 对该登录的权限。这种方法大大优于另一种方法,后者是:
      • 将包含程序集的数据库设置为 TRUSTWORTHY ON。只有在无法对程序集进行签名时,才应将此方法用作最后的手段。或者用于快速测试目的。将数据库设置为 TRUSTWORTHY ON 会使您的实例面临潜在的安全威胁,应该避免,即使比其他方法更快/更容易。
    • 如果您尝试访问您已经登录的 SQL 服务器实例,那么您可以选择使用 Context Connection = true; 的进程内连接可以在 SAFE 程序集中完成。这是@Marc 在他的回答中建议的。虽然使用这种类型的连接肯定有好处,而且在这种特定情况下上下文连接是合适的选择,但说你应该总是使用是过于简单化和不正确的这种类型的连接。让我们看看 Context Connection:

      的积极和消极方面
      • 优点:
        • 可以在 SAFE 程序集中完成。
        • 非常低的连接开销,因为它不是额外的连接。
        • 是当前会话的一部分,因此您执行的任何 SQL 都可以访问基于会话的项目,例如本地临时 tables 和 CONTEXT_INFO.
      • 否定:

        • 如果已启用模拟,则无法使用。
        • 只能连接到当前 SQL 服务器实例。
        • 在函数(标量和 Table-Valued)中使用时,它具有与 T-SQL 函数相同的所有限制(例如,不允许进行副作用操作),除了您可以执行只读存储过程。
        • Table-如果值函数读取结果集,则不允许将其结果流式传输回。

        所有这些 "negatives" 在使用常规/外部连接时都是允许的,即使它是您从中执行此代码的同一实例。

  2. 如果您连接到从中执行此代码的实例并使用外部/常规连接,则无需指定服务器名称,甚至无需使用 localhost。首选语法是 Server = (local),它使用共享内存,而其他语法有时可能使用效率不高的 TCP/IP。

  3. 除非您有非常具体的原因,否则不要使用 Persist Security Info=True;

  4. Dispose()SqlCommand

  5. 是个好习惯
  6. for循环之前调用insertcommand.Parameters.Add()效率更高,然后在循环内部,只需通过firstname.Value =设置值,您已经在做,所以只需将 insertcommand.Parameters.Add() 行移动到 for 行之前。

  7. tel / @tel / listtelnumberINT 而不是 VARCHAR / string。电话号码,就像邮政编码和社会安全号码 (SSN) 一样,是 而不是 号码,即使它们看起来是。 INT 无法存储前导 0 或类似 ex. 的东西来表示 "extension".

  8. 综上所述,即使以上所有内容都已更正,这段代码仍然存在一个巨大的问题需要解决:这个在直接 T-SQL 中执行是一个相当简单的操作,而在 SQLCLR 中执行此操作过于复杂,维护起来更难、成本更高,而且速度更慢。此代码执行 10,000 个单独的事务,而它可以很容易地作为一个基于集合的查询(即一个事务)来完成。您可以将 for 循环包装在一个可以加快速度的事务中,但它仍然总是比基于集合的 T-SQL 方法慢,因为它仍然需要发出 10,000 个单独的 INSERT 语句。您可以使用 SQL Server 2008 中引入的 NEWID()CRYPT_GEN_RANDOM 轻松地在 T-SQL 中随机化。(请参阅 UPDATE 下面的部分)

如果您想了解有关 SQLCLR 的更多信息,请查看我为 SQL Server Central 撰写的系列文章:Stairway to SQLCLR(需要免费注册)。


更新

这是使用问题中的值生成此随机数据的纯 T-SQL 方法。很容易向 4 个 table 变量中的任何一个添加新值(以增加可能组合的数量),因为查询会动态调整随机化范围以适应每个 table 变量中的任何数据(即第 1 - n 行)。

DECLARE @TelNumber TABLE (TelNumberID INT NOT NULL IDENTITY(1, 1),
                          Num VARCHAR(30) NOT NULL);
INSERT INTO @TelNumber (Num) VALUES ('1525407'), ('5423986'), ('1245398'), ('32657891'),
                                    ('123658974'), ('7896534'), ('12354698');

DECLARE @FirstName TABLE (FirstNameID INT NOT NULL IDENTITY(1, 1),
                          Name NVARCHAR(30) NOT NULL);
INSERT INTO @FirstName (Name) VALUES ('Babak'), ('Carolin'), ('Martin'), ('Marie'),
                  ('Susane'), ('Michail'), ('Ramona'), ('Ulf'), ('Dirk'), ('Sebastian');

DECLARE @LastName TABLE (LastNameID INT NOT NULL IDENTITY(1, 1),
                         Name NVARCHAR(30) NOT NULL);
INSERT INTO @LastName (Name) VALUES ('Bastan'), ('Krause'), ('Rosner'),
                  ('Gartenmeister'), ('Rentsch'), ('Benn'), ('Kycik'), ('Leuoth'),
                  ('Kamkar'), ('Kolaee');

DECLARE @Address TABLE (AddressID INT NOT NULL IDENTITY(1, 1),
                        Addr NVARCHAR(100) NOT NULL);
INSERT INTO @Address (Addr) VALUES ('Deutschlan Chemnitz Sonnenstraße 59'), (''),
  ('Deutschland Chemnitz Arthur-Strobel straße 124'),
  ('Deutschland Chemnitz Brückenstraße 3'),
  ('Iran Shiraz Chamran Blvd, Niayesh straße Nr.155'), (''),
  ('Deutschland Berlin Charlotenburg Pudbulesky Alleee 52'),
  ('United State of America Washington DC. Farbod Alle'), ('');

DECLARE @RowsToInsert INT = 10000;

;WITH rowcounts AS
(
  SELECT (SELECT COUNT(*) FROM @TelNumber) AS [TelNumberRows],
         (SELECT COUNT(*) FROM @FirstName) AS [FirstNameRows],
         (SELECT COUNT(*) FROM @LastName) AS [LastNameRows],
         (SELECT COUNT(*) FROM @Address) AS [AddressRows]
), nums AS
(
  SELECT TOP (@RowsToInsert)
         (CRYPT_GEN_RANDOM(1) % rc.TelNumberRows) + 1 AS [RandomTelNumberID],
         (CRYPT_GEN_RANDOM(1) % rc.FirstNameRows) + 1 AS [RandomFirstNameID],
         (CRYPT_GEN_RANDOM(1) % rc.LastNameRows) + 1 AS [RandomLastNameID],
         (CRYPT_GEN_RANDOM(1) % rc.AddressRows) + 1 AS [RandomAddressID]
  FROM   rowcounts rc
  CROSS JOIN msdb.sys.all_columns sac1
  CROSS JOIN msdb.sys.all_columns sac2
)
-- INSERT dbo.Unsprstb(Firstname, Lastname, Tel, Address)
SELECT fn.Name, ln.Name, tn.Num, ad.Addr
FROM   @FirstName fn
FULL JOIN nums
        ON nums.RandomFirstNameID = fn.FirstNameID
FULL JOIN @LastName ln
        ON ln.LastNameID = nums.RandomLastNameID
FULL JOIN @TelNumber tn
        ON tn.TelNumberID = nums.RandomTelNumberID
FULL JOIN @Address ad
        ON ad.AddressID = nums.RandomAddressID;

备注:

  • 需要 FULL JOIN 而不是 INNER JOIN 来获取整个 @RowsToInsert 行数。
  • 由于这种随机化的本质并且没有使用 DISTINCT 过滤掉它们,因此可能存在重复行。但是,DISTINCT 不能与问题中给定的示例数据一起使用,因为每个数组/table 变量中的元素数量仅提供 6300 个唯一组合,并且要求生成的行数为 10,000。如果向 table 变量添加更多值,使得可能的唯一组合总数超过请求的行数,则可以将 DISTINCT 关键字添加到 nums CTE,或者查询可以重组为简单地 CROSS JOIN 所有 table 变量,包括一个 ROW_COUNT() 字段,并使用 ORDER BY NEWID().[=130= 获取 TOP(n) ]
  • INSERT 已被注释掉,因此更容易看出上面的查询产生了所需的结果。只需取消注释 INSERT 即可让查询执行实际的 DML 操作。