Exec SP on Linked server and put that in temp table - sql-server

Need some help on the below issue:
Case 1 : stored procedure is on server 1 - call is from server1
declare #tempCountry table (countryname char(50))
insert into #tempCountry
exec [database1_server1].[dbo].[getcountrylist]
Select * from #tempCountry
Result: successful execution
Case2 : iIf this same stored procedure is being called from a different server using linked server like this :
declare #tempCountry table (countryname char(50))
insert into #tempCountry
exec [database2_server2].[database1_server1].[dbo].[getcountrylist]
Select * from #tempCountry
Result
Msg 7391, level 16, state 2, line 2
The operation could not be performed because OLEDB provider "SQLNCLI" for linkedserver "Server2_Database2" was unable to begin a distributed transaction.
Case 3
But when tried to execute the stored procedure separately [without temp table insertion] like below
exec [database2_server2].[database1_server1].[dbo].[getcountrylist]
Result: that is executing the stored procedure without any error and returning data.
I forgot to mention that am using SQL Server 2005. As per the server administrator, the feature you've suggested that I use is not available in 2005.

You have (I believe) two options here:
To try to avoid the usage of MSDTC (and all these not pleasant things related to Distributed Transactions) by using OPENQUERY rowset function
/assume (here and below) that [database2_server2] is the name of the linked server/
declare #tempCountry table (countryname char(50))
insert into #tempCountry
select * from openquery([database2_server2], '[database1_server1].[dbo].[getcountrylist]')
select * from #tempCountry
OR
You can set the linked server's option Enable Promotion Of Distributed Transaction to False in order to prevent the local transaction to promote the distributed transaction and therefore use of MSDTC:
EXEC master.dbo.sp_serveroption
#server = N'database2_server2',
#optname = N'remote proc transaction promotion',
#optvalue = N'false'
and your original query should work fine:
declare #tempCountry table (countryname char(50))
insert into #tempCountry
exec [database2_server2].[database1_server1].[dbo].[getcountrylist]
select * from #tempCountry

It is possible to avoid Linked Servers altogether. You can create a SQLCLR stored procedure that makes a standard connection to the remote instance (i.e. Database1).
The C# code below is for a SQLCLR Stored Procedure that:
allows for an optional database name. If empty the current database will be the default database, or if provided it will change to that database after connecting (so that the current database can be different than the default database)
allows for optionally using Impersonation. Without impersonation (the default behavior) the connections are made by the Windows Login that the SQL Server Service is running under (i.e. the "Log On As" account in "Services"). This might not be desired as it does typically provide an elevated level of permissions than the caller usually has. Using Impersonation will maintain the security context of the Login executing the stored procedure, if that Login is associated with a Windows Login. A SQL Server Login does not have a security context and will hence get an error if attempting to use Impersonation.
The ability to toggle Impersonation on and off in the code provided here is for testing purposes so it is easier to see the differences between using Impersonation and not using it. When using this code in a real project, there usually would not be a reason to allow the end-user (i.e. the caller) to change the setting. It is generally safer to use Impersonation. But, the main difficulty in using Impersonation is that it is restricted to the local machine, unless the Windows Login is enabled for Delegation in Active Directory.
should be created on the instance that will be calling Server1: Server2 in Database2
requires a PERMISSION_SET of EXTERNAL_ACCESS. This is best handled by:
signing the Assembly in Visual Studio
in [master], create an Asymmetric Key from the DLL
in [master], create a Login from this new Asymmetric Key
GRANT the EXTERNAL ACCESS ASSEMBLY permission to the new Key-based Login
in [Database2], execute the following:
ALTER ASSEMBLY [NoLinkedServer] WITH PERMISSION_SET = EXTERNAL_ACCESS;
should be executed as:
EXEC dbo.RemoteExec N'Server1', N'Database1', 0;
and:
EXEC dbo.RemoteExec N'Server1', N'Database1', 1;
After each execution, run the following and pay attention to those first two fields:
SELECT [login_name], [original_login_name], *
FROM sys.dm_exec_sessions
WHERE LEFT([program_name], 14) = N'Linked Server?';
The C# code:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Security.Principal;
using Microsoft.SqlServer.Server;
public class LinkedServersSuck
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void RemoteExec(
[SqlFacet(MaxSize = 128)] SqlString RemoteInstance,
[SqlFacet(MaxSize = 128)] SqlString RemoteDatabase,
SqlBoolean UseImpersonation)
{
if (RemoteInstance.IsNull)
{
return;
}
SqlConnectionStringBuilder _ConnectionString =
new SqlConnectionStringBuilder();
_ConnectionString.DataSource = RemoteInstance.Value;
_ConnectionString.Enlist = false;
_ConnectionString.IntegratedSecurity = true;
_ConnectionString.ApplicationName =
"Linked Server? We don't need no stinkin' Linked Server!";
SqlConnection _Connection =
new SqlConnection(_ConnectionString.ConnectionString);
SqlCommand _Command = new SqlCommand();
_Command.CommandType = CommandType.StoredProcedure;
_Command.Connection = _Connection;
_Command.CommandText = #"[dbo].[getcountrylist]";
SqlDataReader _Reader = null;
WindowsImpersonationContext _SecurityContext = null;
try
{
if (UseImpersonation.IsTrue)
{
_SecurityContext = SqlContext.WindowsIdentity.Impersonate();
}
_Connection.Open();
if (_SecurityContext != null)
{
_SecurityContext.Undo();
}
if (!RemoteDatabase.IsNull && RemoteDatabase.Value != String.Empty)
{
// do this here rather than in the Connection String
// to reduce Connection Pool Fragmentation
_Connection.ChangeDatabase(RemoteDatabase.Value);
}
_Reader = _Command.ExecuteReader();
SqlContext.Pipe.Send(_Reader);
}
catch
{
throw;
}
finally
{
if (_Reader != null && !_Reader.IsClosed)
{
_Reader.Close();
}
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Close();
}
}
return;
}
}

Related

Refcursor in stored procedure with postgres

I am a rookie/newbie in the postgres data access api. I have worked a bit on oracle, sql server and trying to do what i have done with those dbms
The use is very simple
1) a stored procedure aka function with input params
2) Returning or more ref cursors
3) Using an ent lib wrapper to use the npgsql provider/database with it
4) Doing a data adapter fill and running into the issue with some cursor de-referencing.. it appears though i am inside a tran..
5) I just want to get some simple working sample with the latest npgsql provider..
Here is my function
CREATE OR REPLACE FUNCTION public.geterrorcategories(
v_organizationid integer)
RETURNS refcursor
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE cv_1 refcursor;
BEGIN
open cv_1 for
SELECT errorCategoryId, name, bitFlag
FROM ErrorCategories
ORDER BY name;
RETURN cv_1;
END;
$BODY$;
The code using the enterprise lib api/wrapper is as follows.
/// <summary>
/// Executes GetErrorCategories in case of SQL Server or GetErrorCategories for Oracle
/// </summary>
public static DataTable GetErrorCategoriesAsDataTable(string dbKey ,int? ORGANIZATIONID)
{
DataTable tbl = new DataTable();
Database db = Helper.GetDatabase(dbKey);
using (DbConnection con = db.CreateConnection()){
con.Open();
var tran = con.BeginTransaction();
using (DbCommand cmd = con.CreateCommand()){
cmd.Transaction = tran;
BuildGetErrorCategoriesCommand(db, cmd ,ORGANIZATIONID);
cmd.CommandText = "GetErrorCategories";
try {
Helper.FillDataTable(tbl, db, cmd);
con.Close();
} catch (DALException ) {
throw;
}
}
}
return tbl;
}
The command is built as follows.
private static void BuildGetErrorCategoriesCommand(Database db, DbCommand cmd ,int? ORGANIZATIONID){
Helper.InitializeCommand(cmd, 300, "GetErrorCategories");
db.AddReturnValueParameter(cmd);
db.AddInParameter(cmd, "organizationId", DbType.Int32, ORGANIZATIONID);
db.AddCursorOutParameter(cmd, "CV_1");
}
I am not getting any error. I get only 1 row back which i think is this un_named_portal_1 or something but not the results from my table which my query returns
It is frustrating as i would like to keep my application code the same as much as possible but would like to switch providers at run time. I am using a tweaked 'ent lib' contribution database that was created for npgsql.
Hope this helps to point me to the right areas to look for..
There is absolutely no reason above to declare your PostgreSQL function to return a cursor - you can simply return a table, see the PostgreSQL docs for more info.
Npgsql originally had a feature where it automatically "dereferenced" cursors returned from functions, but this has been removed. For more information about this see this issue (warning, it's long...). Some people are requesting that the feature be returned.

What is the best to remove xp_cmdshell calls from t-sql code

I'm maintaining large t-sql based application.
It has a lot of usages of bcp called through xp_cmdshell.
It is problematic, because xp_cmdshell has the same security context as SQL Server service account and it's more than necessary to the work.
My first idea to get rid of this disadvantage is to use CLR code. CLR is running with permissions of user that called the code.
I created following procedure and it works fine. I can see that it's using permissions of account that is running this code:
public static void RunBCP(SqlString arguments, out SqlString output_msg, out SqlString error_msg, out SqlInt32 return_val) {
output_msg = string.Empty;
error_msg = string.Empty;
try {
var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = "bcp",
Arguments = arguments.ToString(),
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
while (!proc.StandardOutput.EndOfStream) {
output_msg += proc.StandardOutput.ReadLine();
}
return_val = proc.ExitCode;
}
catch (Exception e) {
error_msg = e.Message;
return_val = 1;
}
}
This is good solution because I'm not messing up in BCP calls(arguments are the same). There are no major changes in logic so there is no risk of an error.
Therefore previous call of BCP in T-SQL was looking this way:
declare #ReturnCode int;
declare #cmd varchar(1000);
SELECT #CMD = 'bcp "select FirstName, LastName, DateOfBirth" queryout "c:\temp\OutputFile.csv" -c -t -T -S"(local)"'
EXEC #ReturnCode=xp_cmdshell #CMD,no_output
Now I call it this way:
declare #ReturnCode int;
declare #cmd varchar(1000);
SELECT #CMD = '"select FirstName, LastName, DateOfBirth" queryout "c:\temp\OutputFile.csv" -c -t -T -S"(local)"'
exec DataBase.dbo.up_RunBCP #arguments = #cmd;
So, the question is: is there any other way to get rid of xp_cmdshell bcp code?
I heard that I can use PowerShell(sqlps). But examples I found suggest to create a powershell script.
Can I call such script from t-sql code?
How this code(powershell script) should be stored? As a database object?
Or maybe there is some other way? Not necessary SSIS. Most what I'd like to know is about powershell.
Thanks for any advices.
Your options for data EXPORT are the following:
using xp_cmdshell to call bcp.exe - your old way of bulk copying
using CLR - your new way of bulk copying
SSIS - my preferred way of doing this; here is the example
INSERT INTO OPENROWSET - the interesting alternative you can use if you are either working on 32-bit environment with text/Jet/whatever drivers installed, or you can install 64-bit drivers (e.g. 64-bit ODBC text driver, see Microsoft Access Database Engine 2010 Redistributable)
SQL Server Import/Export wizard - ugly manual way that seldom works in the way you want it to work
using external CSV table - not supported yet (SQL Server 2016 promises it will be...)
HTH
I would use simple Powershell script that does this, something like:
Invoke-SqlCommand -query '...' | ExportTo-Csv ...
Generally, for administrative functions you could add this to Task Scheduler and be done with it. If you need to execute this task as needed, you can do it via xp_cmdshell using schtasks.exe run Task_NAME which might be better for you since it might be easier to express yourself in Powershell then in T-SQL in given context.
Other mentioned thing all require extra tools (SSIS requires VS for example), this is portable with no dependencies.
To call script without xp_cmdshell you should create a job with powershell step and run it from within t-sql.

Merge aspnetdb.mdf with my own database (automatically generated)

I've searched the internet thoroughly but couldn't find a clear answer to the problem. I have got the aspnet.db database. But i want to add my own tables and data to this database. If i try to connect to it with the connection string:
<add name ="ToernooiCompanionDBContext" connectionString ="Data Source= .\SQLEXPRESS; Integrated Security = SSPI; Trusted_Connection=True; Initial Catalog= aspnetdb" providerName ="System.Data.SqlClient"/>
A new database will be created (aspnetdb.mdf) in C:\Program Files\Microsoft SQL Server\MSSQL10.SQLEXPRESS\MSSQL\DATA.
I want the database (which is automatically generated by codefirst) to merge with the existing one in my APP_DATA folder. What am I doing wrong?
I've tried adding AttachDbFilename=|DataDirectory|aspnetdb.mdf and User Instance=true to my connection string, or using the LocalSqlServer connection string which is defined in machine.config, but in all cases this overwrites the existing database. If I remove Initial Catalog=aspnetdb then I get an error that the initial catalog is needed.
I had the same problem but this link got me on the track to something that worked at least for me. I hope this helps someone at least! :)
Create a database
Add the aspnet tables to the new database
Fix the database connections in web.config so they point to the same database
Write some sql that removes all tables except the ones that start with "aspnet_"
Add the sql to the database initializer you write by your self
Add a call to the database initializer in Global.asax.cs
1. Create a database
I usually do this with SQL Server Management Studio. The database I used for this example code is SQL Server 2008R2 but I have done the same with SQL Server Express that you use.
2. Add the aspnet tables to the new database
I use the following tool which if you use it without any command line arguments works like a wizard.
%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_regsql.exe
3. Fix the database connections so they point to the same database
The following two lines are from the test application I made. Notice that the name of the second connectionstring (MyHealthContext) is identical to the name of the DbContext I am using for my code first classes.
DbContext:
public class MyHealthContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<PersonAttribute> PeopleAttributes { get; set; }
}
Web.config
<add name="ApplicationServices" connectionString="Server=localhost\mssql2008r2;Database=MyHealth;Integrated Security=True;" providerName="System.Data.SqlClient"/>
<add name="MyHealthContext" connectionString="Server=localhost\mssql2008r2;Database=MyHealth;Integrated Security=True;" providerName="System.Data.SqlClient"/>
4. SQL that removes all but the aspnetdb-tables
DECLARE #cmdDropConstraints VARCHAR(4000)
DECLARE #cmdDropTables VARCHAR(4000)
-- ======================================================================
-- DROP ALL THE FOREIGN KEY CONSTRAINTS FROM THE TABLES WE WANT TO DROP
-- ======================================================================
DECLARE cursorDropConstraints CURSOR FOR
SELECT
'ALTER TABLE ['+ s.name + '].[' + t.name + '] DROP CONSTRAINT [' + f.name +']'
FROM
sys.foreign_keys f
INNER JOIN sys.tables t ON f.parent_object_id=t.object_id
INNER JOIN sys.schemas s ON t.schema_id=s.schema_id
WHERE
t.is_ms_shipped=0
AND t.name NOT LIKE 'aspnet_%'
AND t.name <> 'sysdiagrams'
OPEN cursorDropConstraints
WHILE 1=1
BEGIN
FETCH cursorDropConstraints INTO #cmdDropConstraints
IF ##fetch_status != 0 BREAK
EXEC(#cmdDropConstraints)
END
CLOSE cursorDropConstraints
DEALLOCATE cursorDropConstraints;
-- ======================================================================
-- DROP ALL THE RELEVANT TABLES SO THAT THEY CAN BE RECREATED
-- ======================================================================
DECLARE cursorDropTables CURSOR FOR
SELECT
'DROP TABLE [' + Table_Name + ']'
FROM
INFORMATION_SCHEMA.TABLES
WHERE
Table_Name NOT LIKE 'aspnet_%'
AND TABLE_TYPE <> 'VIEW'
AND TABLE_NAME <> 'sysdiagrams'
OPEN cursorDropTables
WHILE 1=1
BEGIN
FETCH cursorDropTables INTO #cmdDropTables
IF ##fetch_status != 0 BREAK
EXEC(#cmdDropTables)
END
CLOSE cursorDropTables
DEALLOCATE cursorDropTables;
5. Code for the database initializer:
Replace the "SQL CODE GOES HERE" below with the sql from step 4
public class MyHealthInitializerDropCreateTables : IDatabaseInitializer<MyHealthContext>
{
public void InitializeDatabase(MyHealthContext context)
{
bool dbExists;
using (new TransactionScope(TransactionScopeOption.Suppress))
{
dbExists = context.Database.Exists();
}
if (dbExists)
{
// Remove all tables which are specific to the MyHealthContext (not the aspnetdb tables)
context.Database.ExecuteSqlCommand(#"SQL CODE GOES HERE");
// Create all tables which are specific to the MyHealthContext (not the aspnetdb tables)
var dbCreationScript = ((IObjectContextAdapter)context).ObjectContext.CreateDatabaseScript();
context.Database.ExecuteSqlCommand(dbCreationScript);
Seed(context);
context.SaveChanges();
}
else
{
throw new ApplicationException("No database instance");
}
}
protected virtual void Seed(MyHealthContext context)
{
//TODO: Add code for seeding your database with some initial data...
}
}
6. Code that hooks in your new database initializer
To make sure that the custom database initializer isn't accidentily run in the production environment i added a #if DEBUG statement since I always compile my code in release mode before publishing.
protected void Application_Start()
{
//TODO: Comment out this database initializer(s) before going into production
#if DEBUG
Database.SetInitializer<MyHealthContext>(new MyHealthInitializerDropCreateTables()); // Create new tables in an existing database
#endif
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
Open ASPNetDB db in sql server by attaching as new database
Make Creation scripts of tables / Stored proce3dures from ASPNetDB and run in your own database to create tables in your database
Open web.config of your application and attach application to your own database. Copy the name of connection string
Go to membership area and replace connectionstring name with copied one
Do above step with area as well

EntLib and SQLCLR?

I have quite a few SQLCLR functions and sprocs in my MSSQL 2008 DB. Debugging and logging is always a problem. I have used Microsoft Enterprise Library Logging Application Block a lot in straight C# apps, and was wondering if it was (1) crazy or (2) impossible to layer that into SQLCLR stuff. I really like using a config file to define a rolling text log, Event Log, and SMTP output for different events, so if there is another way to do so, I'm all ears...
Thanks.
It seems like it is possible. I don't know if it's advisable.
One alternative would be defining a trace listener in code, which of course can read the configuration from the database. Another would simple be logging messages to a SQL table and using triggers to enable notifications.
I also do have to question whether the real mistake is that you have CLR stored procs in your database that have so much business logic that they require logging. I'm a big fan of business logic in my database, but I'm wary of CLR stored procs.
If you wanted to use the enterprise application blocks, the config file to edit woulds be C:\Program Files\Microsoft SQL Server\MSSQL10.INSTANCE_NAME\MSSQL\Binn\sqlservr.config. However, sql server does not seem to write values to this file when you restart it. I arrived at this conclusion with the following CLF PROC and UDFs:
using System;
using System.Configuration;
using Microsoft.SqlServer.Server;
namespace LoggedClr
{
public static class AppDomainInfo
{
[SqlFunction]
public static string GetConfigFileName()
{
return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
}
[SqlFunction]
public static string GetAppConfigValue(string key)
{
return ConfigurationManager.AppSettings[key];
}
[SqlProcedure]
public static void SetAppConfigValue(string key, string value)
{
ConfigurationManager.AppSettings[key] = value;
}
}
}
Which I loaded and ran using the following T-SQL:
CREATE DATABASE TestAssembly
GO
USE TestAssembly
GO
ALTER DATABASE TestAssembly SET TRUSTWORTHY ON;
GO
ALTER AUTHORIZATION ON DATABASE::TestAssembly TO test
GO
DROP ASSEMBLY LoggedClr
GO
CREATE ASSEMBLY LoggedClr
from 'C:\justin''s projects\TestClr\LoggedClr\LoggedClr\bin\Debug\LoggedClr.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS
GO
CREATE FUNCTION GetConfigFileName () RETURNS NVARCHAR(MAX) AS
EXTERNAL NAME LoggedClr.[LoggedClr.AppDomainInfo].GetConfigFileName
GO
CREATE FUNCTION GetAppConfigValue (#key nvarchar(max)) RETURNS nvarchar(max) AS
EXTERNAL NAME LoggedClr.[LoggedClr.AppDomainInfo].GetAppConfigValue
GO
CREATE PROCEDURE SetAppConfigValue (#key nvarchar(max), #value nvarchar(max)) AS
EXTERNAL NAME LoggedClr.[LoggedClr.AppDomainInfo].SetAppConfigValue
GO
SELECT dbo.GetConfigFileName()
EXEC dbo.SetAppConfigValue 'justin', 'is a developer'
SELECT dbo.GetAppConfigValue('justin')
This gave me the following results:
------------------------------------------------------------------------------
C:\Program Files\Microsoft SQL Server\MSSQL10.INSTANCE_NAME\MSSQL\Binn\sqlservr.config
(1 row(s) affected)
-------------------------------------------------------------------------
is a developer
(1 row(s) affected)

SQL Server transactions - whole db locked?

I have a problem on specific SQL Server 2008 customer installation. I wrote the code below to simulate the problem which happens in more complex system. There are two connections (each one with own transaction) opened and each connection modifies a table. Modified tables do not relate to each other. On development platform and other existing customer installations the code works fine. Only at one specific customer we have a problem that the second update in nested transaction hangs. I could make a workaround by moving the first update after commit of nested transaction.
I assume in that specific installation the db is configured to lock down the whole db when a transaction is started. But using DBCC useroptions results in very similar output on systems where the code works and this one.
How can I identify what's wrong here ?
Here's DBCC useroptions output from the problematic DB (SQL Server 2008) and my simplified test code:
textsize 2147483647
language Deutsch
dateformat dmy
datefirst 1
lock_timeout -1
quoted_identifier SET
arithabort SET
ansi_null_dflt_on SET
ansi_warnings SET
ansi_padding SET
ansi_nulls SET
concat_null_yields_null SET
isolation level read committed
DbCommand command1 =null, command2 = null;
try
{
const string cs = "Provider=SQLOLEDB.1;...";
// open command and a transaction with default isolation level
command1 = DbAccessFactory.CreateInitialzedCommand("System.Data.OleDb", cs, true);
// select something
command1.CommandText = "select * from plannerOrderHeaders where ...";
DataSet ds = BusinessCasesHelper.Fill(command1, null, "plannerOrderHeaders");
// make some changes in the table
...
// update the table in DB
BusinessCasesHelper.Update(command1, ds, true);
// open command and a transaction with default isolation level on the same CS as command1
command2 = DbAccessFactory.CreateInitialzedCommand("System.Data.OleDb", cs, true);
// select something
command2.CommandText = "select * from mdOmOrders where ...";
ds = BusinessCasesHelper.Fill(command2, null, "mdOmOrders");
// make some changes
...
// update the db
BusinessCasesHelper.Update(command2, ds, true);
command2.Transaction.Commit();
cmd2Commited = true;
command1.Transaction.Commit();
}
catch (Exception e) {...}
And why do you use ""Provider=SQLOLEDB.1" to access MS SQL Server?
And why do you commit instead of closing and disposing?
I can only guess how the mentioned BusinessCasesHelper, DbAccessFactory, etc. are implemented.
But your question implies that your consider your snippet opening transaction inside another transaction in the same context (i.e. on one connection) while I see that they are probably opening two connections which are not being disposed.

Resources