My issue is very similar to this issue.
However, I'm using SQL Server 2005 Service Pack 2 (SP2) (v9.0.3042) and the solution posted there does not work for me. I tried using both connection strings. One is commented out in my code.
I realize I can store all the results in a List or ArrayList in memory and return that. I've done that successfully, but that is not the goal here. The goal is to be able to stream the results as they are available.
Is this possible using my version of SQL Server?
Here's my code :
(Note that the parameters aren't actually being used currently. I did this for debugging)
public static class StoredProcs
{
[SqlFunction(
DataAccess = DataAccessKind.Read,
SystemDataAccess=SystemDataAccessKind.Read,
FillRowMethodName="FillBaseline",
TableDefinition = "[baseline_id] [int], [baseline_name] [nvarchar](256), [description] [nvarchar](max), [locked] [bit]"
)]
public static IEnumerable fnGetBaselineByID(SqlString projectName, SqlInt32 baselineID)
{
string connStr = "context connection=true";
//string connStr = "data source=.;initial catalog=DBName;integrated security=SSPI;enlist=false";
using (SqlConnection conn = new SqlConnection(connStr))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(String.Format(#"
SELECT *
FROM [DBName].[dbo].[Baseline] WITH (NOLOCK)
"), conn))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
yield return new Baseline(reader);
}
}
}
};
}
public static void FillBaseline(Object obj, out SqlInt32 id, out SqlString name, out SqlString description, out bool locked)
{
Baseline baseline = (Baseline)obj;
id = baseline.mID;
name = baseline.nName;
description = baseline.mDescription;
locked = baseline.mLocked;
}
}
Here's part of my SQL deploy script:
CREATE ASSEMBLY [MyService_Stored_Procs]
FROM 'C:\temp\assemblyName.dll'
WITH PERMISSION_SET = SAFE
When I use the connection string "context connection=true" I get this error:
An error occurred while getting new row from user defined Table
Valued Function :
System.InvalidOperationException: Data access is not allowed in
this context. Either the context is a function or method not marked
with DataAccessKind.Read or SystemDataAccessKind.Read, is a callback
to obtain data from FillRow method of a Table Valued Function, or is a
UDT validation method.
When I use the other connection string I get this error:
An error occurred while getting new row from user defined Table
Valued Function :
System.Security.SecurityException: Request for the permission of
type 'System.Data.SqlClient.SqlClientPermission, System.Data,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
failed.
Upon further research and trial and error I found my solution. The article that I mentioned here says
your assembly must be created with permission_set=external_access
This is much easier said than done, but was a good starting point. Simply using that line in place of permission_set=safe gives the error:
CREATE ASSEMBLY for assembly 'assemblyName' failed because assembly
'assemblyName' is not authorized for PERMISSION_SET = EXTERNAL_ACCESS.
The assembly is authorized when either of the following is true: the
database owner (DBO) has EXTERNAL ACCESS ASSEMBLY permission and the
database has the TRUSTWORTHY database property on; or the assembly is
signed with a certificate or an asymmetric key that has a
corresponding login with EXTERNAL ACCESS ASSEMBLY permission.
So the first thing I had to do was sign my dll file. To do that in Visual Studio 2010, you go to the project properties, Signing tab, and check "Sign the assembly" and give it a name. For this example, the name is MyDllKey. I chose not to protect it with a password. Then, of course, I copied the dll file to the sql server: C:\Temp
Using this page as a reference, I created a SQL login based on the above key using these 3 commands:
CREATE ASYMMETRIC KEY MyDllKey FROM EXECUTABLE FILE = 'C:\Temp\MyDll.dll'
CREATE LOGIN MyDllLogin FROM ASYMMETRIC KEY MyDllKey
GRANT EXTERNAL ACCESS ASSEMBLY TO MyDllLogin
Once the login is created as above, I can now create the assembly using this:
CREATE ASSEMBLY [MyDll]
FROM 'C:\Temp\MyDll.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS
Now the only thing left to do is use the proper connection string. Apparently using enlist=false in combination with connection=true is not possible. Here is an example of the connection string I used.
string connStr = #"data source=serverName\instanceName;initial catalog=DBName;integrated security=SSPI;enlist=false";
And it works!
The original problem is due to use of the yield keyword within your function, as explained in this question: SqlFunction fails to open context connection despite DataAccessKind.Read present.
If you avoid using yield (store results in an intermediate array, return the whole lot at the end) the problem goes away.
Alternatively you can do as you describe and avoid using the context connection, but if you do that you have to mark your assembly for external access as you describe. I think that's best described as a workaround, rather than a solution, given you lose some of the benefits available from a context connection and because of all the extra hoops you have to jump through.
In many cases the benefit of being able to use streaming behaviour (yield) does outweigh this pain, but it's still worth considering both options.
Here's the bug on Connect: http://connect.microsoft.com/SQLServer/feedback/details/442200/sql-server-2008-clr-tvf-data-access-limitations-break-existing-code
Googling for this:
Data access is not allowed in this context. Either the context is a
function or method not marked with DataAccessKind.Read or
SystemDataAccessKind.Read, is a callback to obtain data from FillRow
method of a Table Valued Function, or is a UDT validation method.
Led me to this page, but without the answer I needed.
I eventually figured out what it was.
In my CLR Function, I was calling another Method and passing in the values the Function had recieved.
Sounds innocuous, but what I had done was used the same datatypes (SqlChars, SqlBoolean, SqlInt32) for the input-parameters of the Method I added.
private static ArrayList FlatFile(SqlChars Delimeter, SqlChars TextQualifier)
Apparently using these datatypes for anything other than a CLR SqlFunction or SqlProcedure can sometimes give you this type of cryptic error.
Once I removed those datatypes on my new Method and used the C# ones (string, bool, int), the error finally went away.
private static ArrayList FlatFile(string Delimeter, string TextQualifier)
NOTE: This only errored out when I was using Impersonation to grab a file from another Domain.
When I streamed the file over the local Domain, I didn't receive this error, which is what threw me off.
I hope this helps you in your time of need. I blew way too many hours troubleshooting this.
Related
First off, Ill say that this issue is related to my previous post.
However, I'll move everything over here for reference.
The issue I am having is I am still getting the error:
Msg 6522, Level 16, State 1, Procedure PerfInsert, Line 0 [Batch Start Line 31]
A .NET Framework error occurred during execution of user-defined routine or aggregate "PerfInsert":
System.Security.SecurityException: Request failed.
System.Security.SecurityException:
at MiddleMan.MiddleMan.CreateCommand(SqlString tblString, SqlString featureName, SqlString connectionString, SqlString perfionConnectionString, SqlString logFile)
.
Even though I believe I have followed all the steps necessary to set this up correctly. I have even gone so far as to verify that SQL Server has permissions to the directory of the files.
Anyone know what else I can check to see what the missing piece is?
Or do I need to make this an "unsafe" assembly?
C# code:
using Microsoft.SqlServer.Server;
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Diagnostics;
namespace MiddleMan
{
public static class MiddleMan
{
[SqlProcedure(Name = "PerfInsert")]
public static SqlInt32 CreateCommand(SqlString tblString, SqlString featureName, SqlString connectionString, SqlString perfionConnectionString, SqlString logFile)
{
Process compiler = new Process();
compiler.StartInfo.FileName = "C:\\SQL Server C# Functions\\PerfionLoader\\PerfionLoader\\bin\\Release\\PerfionLoader.exe";
compiler.StartInfo.Arguments = tblString.Value + " " + featureName.Value + " " + connectionString.Value + " " + perfionConnectionString.Value + " " + logFile.Value;
//compiler.StartInfo.UseShellExecute = false;
//compiler.StartInfo.RedirectStandardOutput = true;
compiler.Start();
return SqlInt32.Zero;
}
}
}
SQL code(s):
CREATE ASSEMBLY PerfInsert
AUTHORIZATION dbo
FROM '\\bk-int-1\c$\SQL Server C# Functions\MiddleMan\MiddleMan\bin\Release\MiddleMan.dll'
WITH PERMISSION_SET = SAFE
GO
CREATE ASYMMETRIC KEY [Brock.Retail_Brock.Retail_Brock]
AUTHORIZATION [dbo]
FROM EXECUTABLE FILE = '\\bk-int-1\c$\SQL Server C# Functions\MiddleMan\MiddleMan\bin\Release\MiddleMan.dll';
CREATE LOGIN BrokcRetail
FROM ASYMMETRIC KEY [Brock.Retail_Brock.Retail_Brock]
CREATE PROCEDURE PerfInsert
(
#tblString nvarchar(max)
, #featureName nvarchar(max)
, #connectionString nvarchar(max)
, #perfionConnectionString nvarchar(max)
, #logFiel nvarchar(max)
)
AS EXTERNAL NAME PerfInsert.[MiddleMan.MiddleMan].[CreateCommand]
GO
You are using multi-threading so yes, the Assembly 100% needs to have PERMISSION_SET = UNSAFE.
Also, since you already have the Asymmetric Key and associated Login set up (thank you for doing that and not using TRUSTWORTHY ON), you will need to do the following prior to setting the Assembly to UNSAFE:
USE [master];
GRANT UNSAFE ASSEMBLY TO [BrokcRetail];
and then:
USE [{db_containing_assembly_hopefully_not_master];
ALTER ASSEMBLY [PerfInsert] WITH PERMISSION_SET = UNSAFE;
or, if you create the Asymmetric Key-based Login and grant it the UNSAFE ASSEMBLY permission first, then you can simply use UNSAFE instead of SAFE in the CREATE ASSEMBLY statement.
Starting in SQL Server 2017, you will need to create the Asymmetric Key and associated Login before creating the Assembly. The Asymmetric Key and Login go into [master] while the Assembly can go into any DB (including [master], but usually best to not put custom code in there).
If you are already using SQL Server 2017 or newer, and if the code shown in the question is in the actual order in which you are executing it, then I would guess that you have already either set the database to TRUSTWORTHY ON or disabled "CLR strict security". Otherwise you should not have been able to create the Assembly at all without first having the signature-based login created and granted the UNSAFE ASSEMBLY permission. If I am correct about this, you can re-enable "CLR strict security" and/or turn TRUSTWORTHY OFF for that database.
Also, as I noted on your related question (the one linked to in this question), you should be using SqlString instead of SqlChars. SqlString.Value returns a .NET string while SqlChars.Value returns a char[]. Long ago people associated SqlChars with NVARCHAR(MAX), and SqlString with NVARCHAR(1-4000), but that was only due to Visual Studio / SSDT using those mappings as defaults when generating the DDL to publish the Database Project. But there never was any technical / string mapping between them. You can use either .NET type with either T-SQL datatype.
Also, please exercise caution (and lots of testing) when using multi-threading from within SQLCLR.
Please visit SQLCLR Info for more resources related to working with SQLCLR in general.
Related Posts:
System.Web in SQL Server CLR Function (on DBA.StackExchange)
CREATE PROCEDURE gets “Msg 6567, Level 16, State 2” for SQLCLR stored procedure
I am trying to setup my .NET 4.7.1 program that is connecting to a MySQL database 8.0 to use the minimum privileges to run.
The .NET program is using MySql.Data to make connection. The minimum right for a user to execute a stored procedure is typically only EXECUTE privilege. This works fine from MySQL workbench or command line.
Upon running the .NET program this does return the following exception:
System.Data.SqlTypes.SqlNullValueException: 'Data is Null. This method or property cannot be called on Null values.'
To make it easy, I have create a very small demo program to demonstrate the issue.
Setup of the database:
CREATE DATABASE Spike;
CREATE PROCEDURE TestAccess()
BEGIN
END;
CREATE USER Spike#localhost IDENTIFIED WITH mysql_native_password BY 'sample';
GRANT EXECUTE ON PROCEDURE `TestAccess` TO Spike#localhost;
Setup program code:
static void Main(string[] args)
{
using (MySqlConnection conn = new MySqlConnection("Server=localhost;Database=Spike;uid=Spike;pwd=sample"))
{
conn.Open();
Console.WriteLine("Connection open");
MySqlCommand cmd = new MySqlCommand();
cmd.Connection = conn;
cmd.CommandText = "TestAccess";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.ExecuteNonQuery();
Console.WriteLine("Query executed");
}
Console.ReadKey();
}
The crash happens at the line cmd.ExecuteNonQuery();
The stack from the crash is interesting, since it seems to indicate that the information_schema is queried. When logging all statements I can see that the last statement before the exception is:
SELECT * FROM information_schema.routines WHERE 1=1 AND routine_schema LIKE 'Spike' AND routine_name LIKE 'TestAccess'
I cannot grant different rights on information_schema, but I could give more rights on the stored procedure to make more information visible in the routines table, this feels wrong however. Simple tests with granting CREATE and ALTER access also did not work.
Is there something else I can do, without granting too much privileges?
This appears to be a bug in Connector/NET, similar to bug 75301 but a little different. When it's trying to determine parameter metadata for the procedure, it first creates a MySqlSchemaCollection named Procedures with all metadata about the procedure. (This is the SELECT * FROM information_schema.routines WHERE 1=1 AND routine_schema LIKE 'Spike' AND routine_name LIKE 'TestAccess' query you see in your log.)
The Spike user account doesn't have permission to read the ROUTINE_DEFINITION column, so it is NULL. Connector/NET expects this field to be non-NULL and throws a SqlNullValueException exception trying to read it.
There are two workarounds:
1) The first, which you've discovered, is to set CheckParameters=False in your connection string. This will disable retrieval of stored procedure metadata (avoiding the crash), but may lead to harder-to-debug problems calling other stored procedures if you don't get the order and type of parameters exactly right. (Connector/NET can no longer map them for you using the metadata.)
2) Switch to a different ADO.NET MySQL library that doesn't have this bug: MySqlConnector on NuGet. It's highly compatible with Connector/NET, performs faster, and fixes a lot of known issues.
I found an answer with which I am quite pleased. It is changing the connection string by adding CheckParameters=false:
using (MySqlConnection conn = new MySqlConnection("Server=localhost;Database=Spike;uid=Spike;pwd=sample;CheckParameters=false"))
This disables parameter checking, and thereby information_schema queries.
My problem is as follows: I need a clr udf (in C#) to query with a given ad-usr the ad-group membership
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.DirectoryServices.AccountManagement;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlInt32 check_user_is_part_of_ad_grp(SqlString ad_usr, SqlString ad_grp)
{
bool bMemberOf = false;
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find the group in question
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, ad_grp.ToString());
UserPrincipal usr = UserPrincipal.FindByIdentity(ctx, ad_usr.ToString());
if (group != null && usr != null)
{
bMemberOf = usr.IsMemberOf(group);
}
// Put your code here
return new SqlInt32 (bMemberOf ? 1 : 0);
}
}
If I publish the CLR to my SQL Server 2008 (.net 3.5), then I run the udf as follows:
select dbo.check_user_is_part_of_ad_grp('user', 'group')
And I get an error:
Msg 6522, Level 16, State 1, Line 1
A .NET Framework error occurred during execution of user-defined routine or aggregate "check_user_is_part_of_ad_grp":
System.Security.SecurityException: Request for the permission of type 'System.DirectoryServices.DirectoryServicesPermission, System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' failed.
System.Security.SecurityException:
at UserDefinedFunctions.check_user_is_part_of_ad_grp(SqlString ad_usr, SqlString ad_grp)
I set the target framework of my project to 3.5 and the permission level to EXTERNAL_ACCESS. Also the project references (System.DirectoryServices, System.DirectoryServices.AccountManamgement, System.DirectoryServices.Protocols) to EXTERNAL
I appreciate any help
Most likely all of those Assemblies will need to be set to UNSAFE, especially the three System.DirectoryServices* .NET Framework libraries that you imported. Also, since you are importing unsupported .NET Framework libraries, you will need to set the database to TRUSTWORTHY ON in order to get them to work. Setting a Database to TRUSTWORTHY ON is typically something you want to avoid as it is a security risk, but in this case I do not believe that it can be avoided.
That said, I am not sure that you even need to create this function yourself in SQLCLR. If you are just wanting to know if a Login (Windows Logins only, obviously) belongs to a particular Active Directory group, there is a built-in function that should do that for you. The IS_MEMBER function will indicate if the current Login is a member of the specified Windows group (specified as Domain\Group). The difference in how this function works as opposed to the one that you are creating is that it only works for the current Login; you cannot pass any arbitrary Login into it. BUT, it also doesn't require any of the extra effort and security risks that are a part of this SQLCLR solution. So, something to consider :-).
Comment from O.P. on this answer:
Actually, I need to check an arbitrary Login if it's member of a particular group. I even tried to use a stored proc and `OPENQUERY' with a linked server to ADSI, but this only works as Dynamic SQL since I need to inject group and user.
In that case, just make the Dynamic SQL two layers deep instead of the usual one layer. Something along the lines of:
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N'
SELECT *
FROM OPENQUERY([LinkedServer], N''
SELECT *
FROM someResource
WHERE GroupName=N''''' + #Group + N'''''
AND ObjectName=N''''' + #Login + N''''';
'');
';
PRINT #SQL; -- DEBUG
EXEC (#SQL);
In this approach, the query executing OPENQUERY is Dynamic SQL, but the query given to OPENQUERY to execute is a string literal.
An unhandled exception of type 'System.AccessViolationException' occurred in StatCentric.Tracker.Worker.dll
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I've read numerous posts on both Stack Overflow and various blogs and can't seem to find a solution for this.
I'm doing something very basic:
public void Execute(ITrackerRequestModel model)
{
PageviewRequest p = (PageviewRequest)model;
using (var db = new StatCentricEntities())
{
db.SetTimeout(60);
db.sp_Log_PageView2(p.SiteId, p.DateTimeUtc, p.vid, p.QueryString, p.p, p.t);
}
}
But this error pops up every time I try to call db.sp_Log_PageView2. This only seems to happen inside my worker role (I'm using Windows Azure).
Also worthy of note is that I'm using the Windows Azure Emulator and I am on Windows 8.1.
I've tried the following:
Doing a winsocket reset
Disabling JIT debugging (native,script, managed)
Disabling JIT debugging on module load
Followed some old posts to hot fixes that seem to be specific to .NET
2.0 and discontinued.
Did a memory diagnostic with no issues to make sure it wasn't my hardware.
I am running Visual Studio as administrator and connecting to a remote SQL Server Database hosted in Azure.
Any ideas on how to resolve or further diagnose this are appreciated.
This is not real fix but while waiting for fix from Microsoft you can use this workaround.
I have same problem. I also tried everything to solve that issue. After few days I gave up and used manual "workaround". It only took few minutes to copy and convert existing sproc calls to new ones.
Just ignore auto generated functions and manually call stored procedures. You can use auto generated classes for returned data. Copy and modify existing function and you will get easily correct parameter names and types.
Just implement partial class to different file:
public partial class StatCentricEntities
{
public virtual List<sp_Log_PageView2_Result> my_sp_Log_PageView2(
Guid? siteId,
DateTime time,
string param3,
string param4 )
{
return Database.SqlQuery<sp_Log_PageView2_Result>(
"sp_Log_PageView2 #siteId #time #param3 #param4",
new SqlParameter("siteId", siteId),
new SqlParameter("time", time),
new SqlParameter("param3", param3),
new SqlParameter("param4", param4)).ToList();
}
}
I was getting this "Attempted to read or write protected memory exception" error while using a SQL Server stored procedure that had an output parameter of type 'Date'. I tried various things without success and, in the interest of time, settled on the following solution.
1) Remove the output parameter of type date
2) Return a string via a select statement
SELECT CONVERT(char(10), #AsOfDate, 20) AS AsOfDate
3) Convert the string returned from the stored procedure to a DateTime value in C#
DateTime asOfDate = DateTime.Now;
using (var context = new DA.MyEntities())
{
var procResult = context.myStoredProcedure(myParameter).FirstOrDefault();
DateTime.TryParse(procResult, out asOfDate);
}
I'm not super happy with this compromise, but it did allow me to move forward.
I am trying to use a parametrized query to create an index on our Oracle server. I can create the index fine if I use string concatenation, so I believe its not an account or permissions issue. I get the error:
ORA-01036: illegal variable name/number
I can not find any error in the code, but I am sure I am missing something. I am using Oracle.DataAccess.dll version 4.112.3.0. The version the server reports is "Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production"
I have tried removing all the params, except one and get the same error. Any combination of 1 or more params causes the error. The error happens every time without fail. I have used watches to verify that the params are correctly getting set, and I have tried clearing the params before setting. No matter what I do so far, I still get the error. Here is the code:
using (OracleCommand mycom = new OracleCommand())
{
using (OracleConnection Connection = new OracleConnection(connectionString))
{
mycom.BindByName = true;
mycom.CommandText = "CREATE INDEX :indexname on :tablename (:colname)";
mycom.Parameters.Add("indexname", indexName);
mycom.Parameters.Add("tablename", tableName);
mycom.Parameters.Add("colname", colName);
mycom.Connection = Connection;
mycom.CommandType = CommandType.Text;
mycom.Connection.Open();
mycom.ExecuteNonQuery();
}
}
You cannot use bind variables in a DDL statement (like CREATE INDEX). Bind variables are only used in DML statements. You would need to build up the DDL statement in your C# code.
Is this a part of the installation process for your application? Otherwise, it would be exceedingly unusual to want to create an index from an application. Your application shouldn't be creating any database objects outside of the initial installation process.