Besoin d'aide sur le problème ci-dessous:
Cas 1 : la procédure stockée est sur le serveur 1 - l'appel provient du serveur 1
declare @tempCountry table (countryname char(50))
insert into @tempCountry
exec [database1_server1].[dbo].[getcountrylist]
Select * from @tempCountry
Résultat: exécution réussie
Case2 : iSi cette même procédure stockée est appelée à partir d'un serveur différent à l'aide d'un serveur lié, comme ceci:
declare @tempCountry table (countryname char(50))
insert into @tempCountry
exec [database2_server2].[database1_server1].[dbo].[getcountrylist]
Select * from @tempCountry
Résultat
Msg 7391, niveau 16, état 2, ligne 2
L'opération n'a pas pu être effectuée car le fournisseur OLEDB "SQLNCLI" du serveur lié "Server2_Database2" n'a pas pu démarrer une transaction distribuée.
Cas 3
Mais lorsque vous essayez d'exécuter la procédure stockée séparément [sans insertion de table temporaire], comme ci-dessous
exec [database2_server2].[database1_server1].[dbo].[getcountrylist]
Résultat: cela exécute la procédure stockée sans erreur et renvoie des données.
J'ai oublié de mentionner que j'utilise SQL Server 2005. Selon l'administrateur du serveur, la fonctionnalité que vous avez suggérée d'utiliser n'est pas disponible en 2005.
Vous avez (je crois) deux options ici:
Pour essayer d'éviter l'utilisation de MSDTC
(et de toutes les ces choses pas agréables liées aux transactions distribuées) en utilisant OPENQUERY la fonction de jeu de lignes
/ supposons (ici et ci-dessous) que [database2_server2] est le nom du serveur lié} _ /
declare @tempCountry table (countryname char(50))
insert into @tempCountry
select * from openquery([database2_server2], '[database1_server1].[dbo].[getcountrylist]')
select * from @tempCountry
OU
Vous pouvez définir l'option Enable Promotion Of Distributed Transaction
du serveur lié sur False
afin d'empêcher la transaction locale de promouvoir la transaction distribuée et, par conséquent, d'utiliser MSDTC:
EXEC master.dbo.sp_serveroption
@server = N'database2_server2',
@optname = N'remote proc transaction promotion',
@optvalue = N'false'
et votre requête originale devrait fonctionner correctement:
declare @tempCountry table (countryname char(50))
insert into @tempCountry
exec [database2_server2].[database1_server1].[dbo].[getcountrylist]
select * from @tempCountry
Il est possible d'éviter complètement les serveurs liés. Vous pouvez créer une procédure stockée SQLCLR qui établit une connexion standard à l’instance distante (c'est-à-dire, Database1).
Le code C # ci-dessous est destiné à une procédure stockée SQLCLR qui:
permet un nom de base de données facultatif. Si vide, la base de données actuelle sera la base de données par défaut ou, si elle est fournie, elle sera remplacée par cette base de données après la connexion (de sorte que la base de données actuelle puisse être différente de la base de données par défaut).
permet d'utiliser facultativement l'emprunt d'identité. Sans emprunt d'identité (le comportement par défaut), les connexions sont établies par la connexion Windows sous laquelle le service SQL Server est exécuté (c'est-à-dire le compte "Log On As" dans "Services"). Cela n'est peut-être pas souhaitable, car il fournit généralement un niveau d'autorisations élevé par rapport à l'appelant. L'utilisation de l'emprunt d'identité maintiendra le contexte de sécurité de la connexion exécutant la procédure stockée, si cette connexion est associée à une connexion Windows. Une connexion SQL Server n'a pas de contexte de sécurité et recevra donc une erreur si vous tentez d'utiliser l'emprunt d'identité.
La possibilité d'activer et de désactiver l'emprunt d'identité dans le code fourni ici sert à des fins de test. Il est donc plus facile de voir les différences entre utiliser l'emprunt d'identité et ne pas l'utiliser. Lors de l'utilisation de ce code dans un projet réel, il n'y a généralement aucune raison d'autoriser l'utilisateur final (c'est-à-dire l'appelant) à modifier le paramètre. Il est généralement plus sûr d'utiliser l'emprunt d'identité. Cependant, la principale difficulté liée à l'utilisation de l'emprunt d'identité est qu'elle est limitée à la machine locale, sauf si la connexion Windows est activée pour la délégation dans Active Directory.
doit être créé sur l'instance qui appellera Server1
: Server2
dans Database2
nécessite un PERMISSION_SET
de EXTERNAL_ACCESS
. Ceci est mieux géré par:
[master]
, créez une clé asymétrique à partir de la DLL[master]
, créez une connexion à partir de cette nouvelle clé asymétriqueEXTERNAL ACCESS Assembly
à la nouvelle connexion basée sur une clé[Database2]
, exécutez ce qui suit:ALTER Assembly [NoLinkedServer] WITH PERMISSION_SET = EXTERNAL_ACCESS;
devrait être exécuté comme:EXEC dbo.RemoteExec N'Server1', N'Database1', 0;
et:EXEC dbo.RemoteExec N'Server1', N'Database1', 1;
Après chaque exécution, lancez ce qui suit et faites attention aux deux premiers champs:
SELECT [login_name], [original_login_name], *
FROM sys.dm_exec_sessions
WHERE LEFT([program_name], 14) = N'Linked Server?';
Le code C #:
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;
}
}