I would like to create SQL Server CLR stored procedure for inserting some rows in a table in SQL Server 2012.
Here is my c# code:
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();
}
}
}
I can successfully build, deploy and publish my code in SQL Server, but if I run this CLR stored procedure in SQL Server, I see this message:
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)1 retry)
bei System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource
bei System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
bei System.Data.SqlClient.SqlConnection.Open()
bei StoredProcedures.InsertingRows()
How can I solve this problem?
There are several issues going on in this code that need to be addressed:
Regarding the stated question, when you get a System.Security.SecurityException error, that refers to the code trying to reach outside of the database, something that is not allowed in a
SAFEassembly. How you fix this depends on what you are trying to accomplish.PERMISSION_SETofEXTERNAL_ACCESS. In order to set your assembly to anything other thanSAFE, you need to either:EXTERNAL ACCESS ASSEMBLYpermission to that Login. This method is greatly preferred over the other method, which is:TRUSTWORTHY ON. This method should only ever be used as a last resort if it is not possible to sign the assembly. Or for quick testing purposes. Setting a database toTRUSTWORTHY ONopens your instance up to potential security threats and should be avoided, even if quicker / easier than the other method.If you are trying to access the SQL Server instance that you are already logged into, then you have the option of using the in-process connection of
Context Connection = true;which can be done in aSAFEassembly. This is what @Marc suggested in his answer. While there are definitely benefits to using this type of connection, and while the Context Connection was the appropriate choice in this particular scenario, it is overly-simplistic and incorrect to state that you should always use this type of connection. Let's look at the positive and negative aspects of the Context Connection:SAFEassembly.CONTEXT_INFO.Negatives:
All of these "negatives" are allowed when using a regular / external connection, even if it is to the same instance you are executing this code from.
If you are connecting to the instance that you are executing this code from and using an external / regular connection, then no need to specify the Server name or even use
localhost. The preferred syntax isServer = (local)which uses Shared Memory whereas the others might sometimes use TCP/IP which is not as efficient.Unless you have a very specific reason for doing so, don't use
Persist Security Info=True;It is a good practice to
Dispose()of yourSqlCommandIt is more efficient to call the
insertcommand.Parameters.Add()just before theforloop, and then inside of the loop, simply set the value viafirstname.Value =, which you are already doing, so just move theinsertcommand.Parameters.Add()lines to just before theforline.tel/@tel/listtelnumberareINTinstead ofVARCHAR/string. Telephone numbers, just like zip-codes and Social Security Numbers (SSNs), are not numbers, even if they appear to be.INTcannot store leading0s or something likeex.to signify an "extension".All of that being said, even if all of the above is corrected, there is still a huge problem with this code that should be addressed: this is a rather simplistic operation to perform in straight T-SQL, and doing this in SQLCLR is over-complicated, harder and more costly to maintain, and much slower. This code is performing 10,000 separate transactions whereas it could so easily be done as a single set-based query (i.e. one transaction). You could wrap your
forloop in a transaction which would speed it up, but it will still always be slower than the set-based T-SQL approach since it still needs to issue 10,000 separateINSERTstatements. You can easily randomize in T-SQL by using eitherNEWID()or CRYPT_GEN_RANDOM which was introduced in SQL Server 2008. (please see the UPDATE section below)If you want to learn more about SQLCLR, please check out the series I am writing for SQL Server Central: Stairway to SQLCLR (free registration required).
UPDATE
Here is a pure T-SQL method of generating this random data, using the values from the Question. It is easy to add new values to any of the 4 table variables (to increase the number of possible combinations) as the query dynamically adjusts the randomization range to fit whatever data is in each table variable (i.e. rows 1 - n).
Notes:
FULL JOINs are needed instead ofINNER JOINs to get the entire@RowsToInsertamount of rows.DISTINCT. However,DISTINCTcannot be used with the given sample data in the question since the number of elements in each array / table variable provide for only 6300 unique combinations and the requested number of rows to generate is 10,000. If more values are added to the table variables such that the total possible unique combinations rises above the requested number of rows, then either theDISTINCTkeyword can be added to thenumsCTE, or the query can be restructured to simplyCROSS JOINall of the table variable, include aROW_COUNT()field, and grab theTOP(n)usingORDER BY NEWID().INSERTis commented out so it is easier to see that the query above produces the desired result. Just uncomment theINSERTto have the query do the actual DML operation.