Condividi tramite


Funzioni CLR con valori di tabella

Si applica a:SQL Server

Una funzione con valori di tabella è una funzione definita dall'utente che restituisce una tabella.

SQL Server estende la funzionalità delle funzioni con valori di tabella consentendo di definire una funzione con valori di tabella in qualsiasi linguaggio gestito. I dati vengono restituiti da una funzione con valori di tabella tramite un oggetto IEnumerable o IEnumerator.

Per le funzioni con valori di tabella, le colonne del tipo di tabella restituito non possono includere colonne timestamp o colonne di tipo di dati stringa non Unicode , ad esempio char, varchare testo). Il vincolo NOT NULL non è supportato.

Differenze tra Transact-SQL e funzioni clr con valori di tabella

Le funzioni con valori di tabella Transact-SQL materializzano i risultati della chiamata della funzione in una tabella intermedia. Poiché utilizzano una tabella intermedia, possono supportare vincoli e indici univoci sui risultati. Queste funzionalità possono essere utili quando vengono restituiti risultati di grandi dimensioni.

Al contrario, le funzioni CLR (Common Language Runtime) con valori di tabella rappresentano un'alternativa di streaming. Non è necessario materializzare l'intero set di risultati in una singola tabella. L'oggetto IEnumerable restituito dalla funzione gestita viene chiamato direttamente dal piano di esecuzione della query che chiama la funzione con valori di tabella e i risultati vengono utilizzati in modo incrementale. Questo modello di flusso consente di utilizzare i risultati non appena è disponibile la prima riga invece di dover attendere il popolamento dell'intera tabella. È anche un'alternativa migliore se si dispone di un numero elevato di righe restituite, perché non devono essere materializzate in memoria nel suo complesso. Una funzione con valori di tabella gestita, ad esempio, può essere utilizzata per analizzare un file di testo e restituire ogni riga del file come riga di tabella.

Implementare funzioni con valori di tabella

Implementare funzioni con valori di tabella come metodi in una classe in un assembly .NET Framework. Il codice della funzione con valori di tabella deve implementare l'interfaccia IEnumerable. L'interfaccia IEnumerable è definita in .NET Framework. I tipi che rappresentano matrici e raccolte in .NET Framework implementano già l'interfaccia IEnumerable. Questo semplifica la scrittura di funzioni con valori di tabella che convertono una raccolta o una matrice in un set di risultati.

Parametri con valori di tabella

I parametri con valori di tabella sono tipi di tabella definiti dall'utente passati in una procedura o in una funzione che consentono di passare in modo efficiente più righe di dati al server. I parametri con valori di tabella offrono funzionalità simili alle matrici di parametri, ma offrono maggiore flessibilità e maggiore integrazione con Transact-SQL. Consentono inoltre di ottenere prestazioni potenzialmente migliori.

I parametri con valori di tabella aiutano anche a ridurre il numero di round trip al server. Anziché inviare più richieste al server, ad esempio con un elenco di parametri scalari, è possibile inviare i dati al server sotto forma di parametro con valori di tabella. Non è possibile passare un tipo di tabella definito dall'utente come parametro con valori di tabella a o essere restituito da una stored procedure gestita o una funzione in esecuzione nel processo di SQL Server. Per altre informazioni sui parametri con valori di tabella, vedere Usare parametri con valori di tabella (motore di database).

Parametri di output e funzioni con valori di tabella

Le informazioni potrebbero essere restituite dalle funzioni con valori di tabella usando i parametri di output. Il parametro corrispondente nella funzione con valori di tabella nel codice di implementazione deve utilizzare un parametro di passaggio per riferimento come argomento. Visual Basic .NET non supporta i parametri di output nello stesso modo in cui C# esegue. È necessario specificare il parametro per riferimento e applicare l'attributo <Out()> per rappresentare un parametro di output, come nell'esempio seguente:

Imports System.Runtime.InteropServices
...
Public Shared Sub FillRow ( <Out()> ByRef value As SqlInt32)

Definire una funzione con valori di tabella in Transact-SQL

La sintassi per la definizione di una funzione CLR con valori di tabella è simile a quella di una funzione con valori di tabella Transact-SQL, con l'aggiunta della clausola EXTERNAL NAME. Ad esempio:

CREATE FUNCTION GetEmpFirstLastNames()
RETURNS TABLE (
    FirstName NVARCHAR (4000),
    LastName NVARCHAR (4000)
)
AS EXTERNAL NAME MyDotNETAssembly.[MyNamespace.MyClassname].GetEmpFirstLastNames;

Le funzioni con valori di tabella vengono utilizzate per rappresentare i dati in formato relazionale per un'ulteriore elaborazione nelle query, come nell'esempio seguente:

SELECT *
FROM func();

SELECT *
FROM tbl
     INNER JOIN func() AS f
         ON tbl.col = f.col;

SELECT *
FROM tbl AS t
CROSS APPLY func(t.col);

Le funzioni con valori di tabella possono restituire una tabella nei casi seguenti:

  • Vengono create da argomenti di input scalari. Una funzione con valori di tabella che utilizza, ad esempio, una stringa di numeri delimitati da virgole e trasforma i numeri in tabella tramite Pivot.

  • Vengono generate da dati esterni. Una funzione con valori di tabella che legge, ad esempio, il log eventi e lo espone come tabella.

Nota

Una funzione con valori di tabella può eseguire l'accesso ai dati solo tramite una query Transact-SQL nel metodo InitMethod e non nel metodo FillRow. Il InitMethod deve essere contrassegnato con la proprietà dell'attributo SqlFunction.DataAccess.Read se viene eseguita una query Transact-SQL.

Una funzione di esempio con valori di tabella

La funzione con valori di tabella seguente restituisce informazioni dal registro eventi di sistema. La funzione accetta un singolo argomento stringa contenente il nome del registro eventi da leggere.

Codice di esempio

using System;
using System.Data.Sql;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Data.SqlTypes;
using System.Diagnostics;

public class TabularEventLog
{
    [SqlFunction(FillRowMethodName = "FillRow")]
    public static IEnumerable InitMethod(String logname)
    {
        return new EventLog(logname).Entries;
    }

    public static void FillRow(Object obj, out SqlDateTime timeWritten, out SqlChars message, out SqlChars category, out long instanceId)
    {
        EventLogEntry eventLogEntry = (EventLogEntry)obj;
        timeWritten = new SqlDateTime(eventLogEntry.TimeWritten);
        message = new SqlChars(eventLogEntry.Message);
        category = new SqlChars(eventLogEntry.Category);
        instanceId = eventLogEntry.InstanceId;
    }
}

Dichiarare e usare la funzione con valori di tabella di esempio

Dopo la compilazione della funzione con valori di tabella di esempio, può essere dichiarata in Transact-SQL simile alla seguente:

USE master;
-- Replace SQL_Server_logon with your SQL Server user credentials.

GRANT EXTERNAL ACCESS ASSEMBLY TO [SQL_Server_logon];

-- Modify the following line to specify a different database.
ALTER DATABASE master
SET TRUSTWORTHY ON;

-- Modify the next line to use the appropriate database.
CREATE ASSEMBLY tvfEventLog
    FROM 'D:\assemblies\tvfEventLog\tvfeventlog.dll'
    WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO

CREATE FUNCTION ReadEventLog
(@logname NVARCHAR (100))
RETURNS TABLE (
    logTime DATETIME,
    Message NVARCHAR (4000),
    Category NVARCHAR (4000),
    InstanceId BIGINT)
AS EXTERNAL NAME tvfEventLog.TabularEventLog.InitMethod;
GO

Gli oggetti di database di Visual C++ compilati con /clr:pure non sono supportati per l'esecuzione in SQL Server. Tali oggetti di database, ad esempio, includono funzioni con valori di tabella.

Per testare l'esempio, provare il codice Transact-SQL seguente:

-- Select the top 100 events,
SELECT TOP 100 *
FROM dbo.ReadEventLog(N'Security') AS T;
GO

-- Select the last 10 login events.
SELECT TOP 10 T.logTime,
              T.Message,
              T.InstanceId
FROM dbo.ReadEventLog(N'Security') AS T
WHERE T.Category = N'Logon/Logoff';
GO

Esempi

Gli esempi di codice Transact-SQL in questo articolo usano il database di esempio AdventureWorks2022 o AdventureWorksDW2022, che è possibile scaricare dalla home page Microsoft SQL Server Samples and Community Projects.

Un. Restituire i risultati di una query di SQL Server

L'esempio seguente illustra una funzione con valori di tabella che esegue query su un database di SQL Server.

Assegnare al file di codice sorgente il nome FindInvalidEmails.cs o FindInvalidEmails.vb.

using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;

using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions {
   private class EmailResult {
      public SqlInt32 CustomerId;
      public SqlString EmailAdress;

      public EmailResult(SqlInt32 customerId, SqlString emailAdress) {
         CustomerId = customerId;
         EmailAdress = emailAdress;
      }
   }

   public static bool ValidateEmail(SqlString emailAddress) {
      if (emailAddress.IsNull)
         return false;

      if (!emailAddress.Value.EndsWith("@adventure-works.com"))
         return false;

      // Validate the address. Put any more rules here.
      return true;
   }

   [SqlFunction(
       DataAccess = DataAccessKind.Read,
       FillRowMethodName = "FindInvalidEmails_FillRow",
       TableDefinition="CustomerId int, EmailAddress nvarchar(4000)")]
   public static IEnumerable FindInvalidEmails(SqlDateTime modifiedSince) {
      ArrayList resultCollection = new ArrayList();

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

         using (SqlCommand selectEmails = new SqlCommand(
             "SELECT " +
             "[CustomerID], [EmailAddress] " +
             "FROM [AdventureWorksLT2022].[SalesLT].[Customer] " +
             "WHERE [ModifiedDate] >= @modifiedSince",
             connection)) {
            SqlParameter modifiedSinceParam = selectEmails.Parameters.Add(
                "@modifiedSince",
                SqlDbType.DateTime);
            modifiedSinceParam.Value = modifiedSince;

            using (SqlDataReader emailsReader = selectEmails.ExecuteReader()) {
               while (emailsReader.Read()) {
                  SqlString emailAddress = emailsReader.GetSqlString(1);
                  if (ValidateEmail(emailAddress)) {
                     resultCollection.Add(new EmailResult(
                         emailsReader.GetSqlInt32(0),
                         emailAddress));
                  }
               }
            }
         }
      }

      return resultCollection;
   }

   public static void FindInvalidEmails_FillRow(
       object emailResultObj,
       out SqlInt32 customerId,
       out SqlString emailAdress) {
      EmailResult emailResult = (EmailResult)emailResultObj;

      customerId = emailResult.CustomerId;
      emailAdress = emailResult.EmailAdress;
   }
};

Compilare il codice sorgente in una DLL e copiare la DLL nella directory radice dell'unità C. Eseguire quindi la query Transact-SQL seguente.

USE AdventureWorksLT2022;
GO

IF EXISTS (SELECT name
           FROM sysobjects
           WHERE name = 'FindInvalidEmails')
    DROP FUNCTION FindInvalidEmails;
GO

IF EXISTS (SELECT name
           FROM sys.assemblies
           WHERE name = 'MyClrCode')
    DROP ASSEMBLY MyClrCode;
GO

CREATE ASSEMBLY MyClrCode
    FROM 'C:\FindInvalidEmails.dll'
    WITH PERMISSION_SET = SAFE;
GO

CREATE FUNCTION FindInvalidEmails
(@ModifiedSince DATETIME)
RETURNS TABLE (
    CustomerId INT,
    EmailAddress NVARCHAR (4000))
AS EXTERNAL NAME MyClrCode.UserDefinedFunctions.[FindInvalidEmails];
GO

SELECT *
FROM FindInvalidEmails('2000-01-01');
GO