1

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

这是我的 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 Server 中成功构建、部署和发布我的代码,但是如果我在 SQL Server 中运行此 CLR 存储过程,我会看到以下消息:

消息 6522,级别 16,状态 1,过程 InsertingRows,第 0
行在执行用户定义的例程或聚合“InsertingRows”期间发生 .NET Framework 错误:
System.Security.SecurityException:对“System.Data”类型权限的错误请求。 SqlClient.SqlClientPermission,System.Data,版本=4.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089"。
System.Security.SecurityException:
北 System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
北 System.Security.PermissionSet.Demand()
北 System.Data.Common.DbConnectionOptions.DemandPermission()
北 System.Data .SqlClient.SqlConnectionFactory.PermissionDemand(DbConnection outerConnection)
bei System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource 1 retry) bei System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry) bei System.Data.SqlClient.SqlConnection.Open() bei StoredProcedures.InsertingRows() 1 retry, DbConnectionOptions userOptions)
bei System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource



我怎么解决这个问题?

4

2 回答 2

4

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

  1. 关于所述问题,当您收到System.Security.SecurityException错误时,它指的是代码试图到达数据库之外,这是程序集中不允许的SAFE。你如何解决这个问题取决于你想要完成什么。

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

      • 正面:
        • 可以在一个SAFE程序集中完成。
        • 非常低的连接开销(如果有的话),因为它不是额外的连接。
        • 是当前会话的一部分,因此您执行的任何 SQL 都可以访问基于会话的项目,例如本地临时表和CONTEXT_INFO.
      • 负面:

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

        使用常规/外部连接时,所有这些“负面因素”都是允许的,即使它与您正在执行此代码的实例相同。

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

  3. 除非您有非常具体的理由这样做,否则不要使用Persist Security Info=True;

  4. Dispose()这是你的一个好习惯SqlCommand

  5. insertcommand.Parameters.Add()在循环之前调用更有效for,然后在循环内部,只需设置值 via firstname.Value =,您已经在执行此操作,因此只需将行移动到insertcommand.Parameters.Add()行之前for

  6. tel//代替/ 。@tel_ listtelnumber_ 电话号码,就像邮政编码和社会安全号码 (SSN) 一样,不是数字,即使它们看起来是。不能存储前导或类似的东西来表示“扩展”。INTVARCHARstringINT0ex.

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

如果您想了解有关 SQLCLR 的更多信息,请查看我为 SQL Server Central 编写的系列:通往 SQLCLR 的阶梯(需要免费注册)。


更新

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

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;

笔记:

  • 需要 s而FULL JOIN不是INNER JOINs 来获取全部@RowsToInsert行数。
  • 由于这种随机化的性质而不是通过使用过滤它们,因此可以重复行DISTINCT。但是,DISTINCT由于每个数组 /表变量中的元素数量仅提供6300个唯一组合,因此要生成的行数为10,000,因此无法与给定的示例数据一起使用。如果将更多值添加到表变量中,以使可能的总唯一组合升高到请求的行数以上,则可以将DISTINCT关键字添加到numsCTE中,或者可以将查询重组为简单CROSS JOIN的所有表变量,包括一个ROW_COUNT()字段,抓住TOP(n)使用ORDER BY NEWID()
  • 评论了,因此INSERT更容易看到上面的查询会产生所需的结果。只需INSERT输入查询即可完成实际的DML操作。
于 2015-07-24T06:57:54.110 回答
1

在您的 SQL CLR C# 代码中,您不应使用显式服务器、数据库名称和凭据建立连接 - 而是使用“上下文”连接:

using (SqlConnection conn = new SqlConnection("context connection=true"))
{
    // do your stuff here...
}    
于 2015-07-23T09:13:12.723 回答