2016-02-23 2 views
4

J'ai écrit un certain nombre de fonctions SQL CLR (UDF) qui lisent des données à partir d'une base de données DB2 externe hébergée sur IBM iSeries. IBM DB2 .Net Provider). Pour que la fonction dispose des autorisations nécessaires pour lire ces données, je dois décorer la fonction avec l'attribut SqlFunction ayant la propriété DataAccess définie sur DataAccessKind.Read. Je déploie également l'assemblée en tant que UNSAFE.Comment autoriser une fonction SQL CLR à s'exécuter dans un plan de requête parallèle et disposer également d'autorisations d'accès aux données

Le temps requis pour lire les données de la base de données DB2 est relativement lent (par exemple, 3 ms pour l'ExecuteScalar le plus simple). J'utilise ces fonctions définies par l'utilisateur pour fusionner efficacement les données de la base de données DB2 dans les vues de serveur SQL Server.

Par exemple, supposons que mon UDF est défini comme

[SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = true)] 
public static SqlMoney GetCostPrice(SqlString partNumber) 
{ 
    decimal costPrice; 
    // open DB2 connection and retrieve cost price for part 
    return new SqlMoney(costPrice); 
} 

puis utilisez dans mon View SQL comme:

select Parts.PartNumber, 
     dbo.GetCostPrice(Parts.PartNumber) as CostPrice 
from Parts 

Les problèmes de mauvaise performance pourrait être significativement impacté si je pouvais courir mon Vues SQL avec un plan de requête parallèle.

Des techniques documentées expliquent comment forcer un plan de requête à s'exécuter en parallèle plutôt qu'en série, mais ces techniques ont des limitations imposées par SQL Server, dont une fonction SQL CLR définie DOIT avoir DataAccess = DataAccessKind.None. Mais si je mets DataAcessKind à None, alors je reçois une exception lorsque j'essaie d'ouvrir une connexion DbConnection dans la fonction.

Et c'est mon problème! Comment puis-je exécuter mon fichier UDF dans un plan de requête parallèle tout en lui permettant de lire les données de la base de données externe? La meilleure idée que je dois aborder est de hard-code DataAccess = DataAccessKind.None dans mon attribut SqlFunction et puis, à l'exécution, dans le corps de la fonction élever des autorisations avec Code Access Security de sorte que le code suivant a des autorisations pour ouvrir les objets DbConnection.

Mais je n'arrive pas à comprendre comment faire? A titre d'expérience, je l'ai essayé ce qui suit

[SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)] 
    public static SqlMoney TestFunction() 
    { 
     var sqlPerm = new SqlClientPermission(PermissionState.Unrestricted); 
     sqlPerm.Assert(); 

     using (var conn = new SqlConnection("context connection=true")) 
     { 
      conn.Open(); 
     } 

     return new SqlMoney(); 
    } 

et invoquer de SQL Server Management Studio avec:

select dbo.TestFunction() 

mais je continue à obtenir une exception de sécurité ...

A. Une erreur NET Framework s'est produite pendant l'exécution de la routine ou de l'agrégat "TestFunction" définie par l'utilisateur: System.InvalidOperationException: L'accès aux données n'est pas autorisé dans ce contexte. Soit le contexte est une fonction ou une méthode non marquée avec DataAccessKind.Read ou SystemDataAccessKind.Read, est un rappel pour obtenir des données à partir de la méthode FillRow d'une fonction de valeur table, ou est une méthode de validation UDT. System.InvalidOperationException: à System.Data.SqlServer.Internal.ClrLevelContext.CheckSqlAccessReturnCode (SqlAccessApiReturnCode Erc) à System.Data.SqlServer.Internal.ClrLevelContext.GetCurrentContext (SmiEventSink évier, Boolean throwIfNotASqlClrThread, Boolean fAllowImpersonation) à Microsoft.SqlServer .Server.InProcLink.GetCurrentContext (SmiEventSink eventSink) à Microsoft.SqlServer.Server.SmiContextFactory.GetCurrentContext() à System.Data.SqlClient.SqlConnectionFactory.GetContextConnection (options de sqlConnectionString, objet providerInfo, DbConnection owningConnection) à System.Data.SqlClient.SqlConnectionFactory.CreateConnection (options DbConnectionOptions, objet poolGroupProviderInfo, piscine DbConnectionPool, DbConnection owningConnection) à System.Data.ProviderBase.DbConnectionFactory.CreateNonPooledConnection (DbConnection owningConnection, DbConnectionPoolGroup poolGroup) à System.Data.ProviderBase.DbConnectionFactory.GetConnection (DbConnection owningConnection) à System.Data.ProviderBase.DbConnectionClosed.OpenConnection (DbConnection outerConnection, DbConnectionFactory connectionFactory) à System.Data.SqlClient.SqlConnection.Open () à UserDefinedFunctions.UserDefinedFunctions.TestFunction()

Quelqu'un a-t-il des idées?

Merci d'avance.

(BTW, je suis en cours d'exécution sur SQL 2008 en utilisant .Net 3.5)

+0

Pourquoi ne pas utiliser une base de données connectée? –

+0

Que voulez-vous dire "base de données connectée"? Vous voulez dire un serveur lié ??? (Désolé si je suis épais!) – Kev

+0

https://msdn.microsoft.com/en-us/library/ms188279.aspx –

Répondre

1

En ce qui concerne mes tests (contre SqlConnection à SQL Server) montre, cela ne peut être accompli en utilisant une connexion régulière/externe (pasContext Connection = true) et ajouter le mot-clé Enlist à la chaîne de connexion, mis à false:

Server=DB2; Enlist=false; 

Mais il ne semble pas être un moyen de faire ce travail lors de l'utilisation Context Connection = true. La connexion de contexte fait automatiquement partie de la transaction en cours et vous ne pouvez pas spécifier d'autres mots-clés de chaîne de connexion lors de l'utilisation de la connexion de contexte. Qu'est-ce que la transaction a à voir avec cela? Eh bien, la valeur par défaut pour Enlist est true, même si vous avez une connexion régulière/externe, si vous ne spécifiez pas Enlist=false;, vous obtenez le même

L'accès aux données ne sont pas permis dans ce contexte.

erreur que vous obtenez maintenant. Bien sûr, il s'agit d'un point discutable car il est inutile d'utiliser la connexion contextuelle dans ce cas particulier, car il faudrait alors utiliser un serveur lié, et il a été souligné dans un commentaire sur la question que le " Les fournisseurs Db2 de serveur lié (de MS) sont incroyablement lents ".

Il a également été souligné que peut-être en utilisant TransactionScope avec une option de Suppress pourrait fonctionner. Cela ne peut pas fonctionner car vous n'êtes pas autorisé à instancier un objet TransactionScope (avec l'une des trois options: Required, RequiresNew ou Suppress) si les deux DataAccess et SystemDataAccess sont définis sur None (qui est leur valeur par défaut).

En outre, en ce qui concerne le désir de

élever le statut de UserDataAccess d'une UDF lors de l'exécution.

Ceci n'est simplement pas possible car UserDataAccess n'est pas une option d'exécution. Il est déterminé lorsque l'instruction CREATE FUNCTION est exécutée (celle qui a la valeur AS EXTERNAL NAME [Assembly]... comme définition.Les propriétés UserDataAccess et SystemDataAccess sont des métadonnées stockées avec la fonction. Vous pouvez voir le réglage de l'un de ceux-ci en utilisant la OBJECTPROPERTYEX fonction intégrée:

SELECT OBJECTPROPERTYEX(OBJECT_ID(N'SchemaName.FunctionName'), 'UserDataAccess'); 

Vos deux options semblent être:

  1. Utilisez un fournisseur qui prend en charge le mot-clé Enlist de sorte qu'il peut être défini sur false ou s'il ne s'enroule pas par défaut, il ne requiert pas autrement DataAccess pour être défini sur Read. Selon la documentation proposée pour examiner (Integrating DB2 Universal Universal Database for iSeries with for iSeries with Microsoft ADO .NET), les options semblent être:

    • OleDb
    • ODBC
    • IBM DB2 pour LUW .NET
  2. Construire un être mi-déchirure un service Web auquel une fonction SQLCLR peut transmettre la requête, il utilisera n'importe quel fournisseur pour obtenir l'information, et il répondra avec l'information. La fonction SQLCLR ne fait alors aucun accès direct aux données, et le service Web peut faire sa propre mise en cache (vous avez dit que les données sources ne changent pas souvent) pour améliorer les performances (même si vous ne mettez en cache que les valeurs 1 - 5 minutes). Oui, cela introduit une dépendance externe, mais cela devrait fonctionner autrement.

+0

Merci pour la réponse. J'ai quitté la propriété IsDeterministric par défaut pour des raisons de concision, mais vous avez raison d'avoir induit le lecteur en erreur (je modifierai la question en un mo). En réalité je l'ai mis à vrai - les données de source ne changent pas souvent et si la mise en cache manque un changement dans les données de source (très peu de chance) alors ceci n'a aucun impact significatif. L'ensemble d'autorisations SAFE n'est pas requis, j'ai prouvé que je peux générer un plan parallèle avec une assemblée UNSAFE. – Kev

+0

@Kev Intéressant de le faire dans UNSAFE. Je n'ai jamais vu ça. Il n'y a pas de chemin direct autour de la partie DataAccess; cela est même utilisé pour la fonction T-SQL (regardez 'OBJECTPROPERTYEX (object_id, 'UserDataAccess')'). J'ai une ou deux idées que je peux essayer plus tard et mettre à jour avec les résultats ce soir .. –

+0

@Kev J'ai mis à jour ma réponse après avoir effectué quelques tests. –