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.
Related
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.
all.
Here is my code.
private void FillData(DataTable data, FilterModel filter){
var cmdStr = MySqlCmdGenerator.Generate(filter);
using (var cmd = new SqlCommand(cmdStr, connection_))
using (var da = new SqlDataAdapter(cmd)){
da.Fill(data);
}
}
public DataTable GetData(IEnumerable<FilterModel> filters){
var data = new DataTable("mydata");
foreach (var filter in filters){
FillData(data);
}
return data;
}
It's a very simple code, which just use SqlDataAdapter to Fill datatable according to the filters.
The problem is that sometimes GetData method returns empty datatable.
First I execute the generated query on SQL Server Management Studio, and it returns correct result (it contains about 100,000 records).
Then I have used Sql Server Profiler to profile what happened in server, and get some errors:
Sort Warnings
RPC:Completed (TextData: Query with a given filter)
Hash Warning
Sort Warnings
RPC:Completed (TextData: Query with a given filter)
User Error Message (TextData: The statement has been terminated.)
Attention
Audit Logout
RPC:Completed (TextData: sp_reset_connection)
(occurs in this order)
After google the error but I could not figure out what is exactly happen in server and application.
Any advice will be appreciated.
Thanks!
Differences between results from a .NET application and those from SQL Server Management Studio are often due to different configuration of ANSI settings for the SQL Server connection.
This assumes you've eliminated the obvious error causes, e.g. you're inadvertently connection to different databases from the two environments.
You can see the settings used by SQL Server Management Studio in Tools / Options / Query Execution / SQL Server / ANSI. On my installation they default to:
SET ANSI_NULL_DFLT_ON ON
SET ANSI_PADDING ON
SET ANSI_NULLS ON
I suggest you try switching these OFF one by one in Management Studio to attempt to reproduce the configuration used by .NET. Once you understand what's happening, you can do one of:
Add SET statements to your .NET query
Modify your query so it isn't dependent on these settings (may need to add ISNULL or explicit tests for NULL).
I am trying to execute a sql script read from a file; however, when I go to execute the script I am met with the exception:
Column, parameter, or variable #1: Cannot find data type dbo.ExistingTableType.
Must declare the table variable "#existingTableType".
Parameter or variable '#existingTableType' has an invalid data type.
I have tried testing the script by copying the text and running it in SQL server management studio and it does run successfully. Additionally, other scripts are able to run but for some reason those with a user-defined table type as a parameter are not. Below is a simple example:
C#
string script = scriptFile.OpenText().ReadToEnd();
SqlConnection connection = new SqlConnection(connectionString);
Server server = new Server(new ServerConnection(connection));
server.ConnectionContext.StatementTimeout = 100;
server.ConnectionContext.ExecuteNonQuery(script);
SQL
CREATE PROCEDURE [dbo].[failedStoredProcedure]
(
#existingTableType dbo.ExistingTableType READONLY
)
AS
-- Do something here
GO
Other tests done:
Tried another user in connection string.
Edit: Additionally, when scripts run it returns 'Invalid Object Name {x}' when the object does exist.
I tried your code on my end (SQL 2012 Express) and it worked just fine. Does the sql account has grants to the type?
It seems that at some point something happens to the SMO object where it no longer applies to the specified database, despite the database name being the intended object.
To correct this I created a new database object and applied the scripts from there:
Server server = new Server();
server.ConnectionContext.ConnectionString = connectionString;
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString);
Database db = server.Databases[builder.InitialCatalog];
Then later instead run
db.ExecuteNonQuery(script);
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.
My development machine is running Windows 7 Enterprise, 64-bit version. I am using Visual Studio 2010 Release Candidate. I am connecting to an Oracle 11g Enterprise server version 11.1.0.7.0. I had a difficult time locating Oracle client software that is made for 64-bit Windows systems and eventually landed here to download what I assume is the proper client connectivity software. I added a reference to "Oracle.DataAccess" which is version 2.111.6.0 (Runtime Version is v2.0.50727). I am targeting .NET CLR version 4.0 since all properties of my VS Solution are defaults and this is 2010 RC. I was then able to write a console application in C# that established connectivity, executed a SELECT statement, and properly returned data when the table in question does NOT contain a spatial column. My problem is that this no longer works when the table I query has a column of type SDO_GEOMETRY in it.
Below is the simple console application I am trying to run that reproduces the problem. When the code gets to the line with the "ExecuteReader" command, an exception is raised and the message is "Unsupported column datatype".
using System;
using System.Data;
using Oracle.DataAccess.Client;
namespace ConsoleTestOracle
{
class Program
{
static void Main(string[] args)
{
string oradb = string.Format("Data Source={0};User Id={1};Password={2};",
"hostname/servicename", "login", "password");
try
{
using (OracleConnection conn = new OracleConnection(oradb))
{
conn.Open();
OracleCommand cmd = new OracleCommand();
cmd.Connection = conn;
cmd.CommandText = "select * from SDO_8307_2D_POINTS";
cmd.CommandType = CommandType.Text;
OracleDataReader dr = cmd.ExecuteReader();
}
}
catch (Exception e)
{
string error = e.Message;
}
}
}
}
The fact that this code works when used against a table that does not contain a spatial column of type SDO_GEOMETRY makes me think I have my windows 7 machine properly configured so I am surprised that I get this exception when the table contains different kinds of columns. I don't know if there is some configuration on my machine or the Oracle machine that needs to be done, or if the Oracle client software I have installed is wrong, or old and needs to be updated.
Here is the SQL I used to create the table, populate it with some rows containing points in the spatial column, etc. if you want to try to reproduce this exactly.
SQL Create Commands:
create table SDO_8307_2D_Points (ObjectID number(38) not null unique, TestID number, shape SDO_GEOMETRY);
Insert into SDO_8307_2D_Points values (1, 1, SDO_GEOMETRY(2001, 8307, null, SDO_ELEM_INFO_ARRAY(1, 1, 1), SDO_ORDINATE_ARRAY(10.0, 10.0)));
Insert into SDO_8307_2D_Points values (2, 2, SDO_GEOMETRY(2001, 8307, null, SDO_ELEM_INFO_ARRAY(1, 1, 1), SDO_ORDINATE_ARRAY(10.0, 20.0)));
insert into user_sdo_geom_metadata values ('SDO_8307_2D_Points', 'SHAPE', SDO_DIM_ARRAY(SDO_DIM_ELEMENT('Lat', -180, 180, 0.05), SDO_DIM_ELEMENT('Long', -90, 90, 0.05)), 8307);
create index SDO_8307_2D_Point_indx on SDO_8307_2D_Points(shape) indextype is mdsys.spatial_index PARAMETERS ('sdo_indx_dims=2' );
Any advice or insights would be greatly appreciated. Thank you.
Here is a link to a post with a sample app using C# and ODP.net to access spatial types.
http://www.orafaq.com/forum/mv/msg/27794/296419/0/#msg_296419
There is also a sample here about using XML to select the spatial types:
http://forums.oracle.com/forums/thread.jspa?threadID=241076