Why are table valued parameters to SQL Server stored procedures required to be input READONLY?

前端 未结 5 2041
广开言路
广开言路 2020-12-17 18:45

Can anyone explain the design decision behind preventing table valued parameters from being specified as output parameters to stored procedures?

I can\'t count the n

5条回答
  •  旧巷少年郎
    2020-12-17 19:17

    Be Forewarned. This code will not work. That is the problem

    Note that all code was entered directly into post from memory. I may have a type wrong in the example or some similar error. It is just to demonstrate the technique that this would facilitate, which won't work with any version of SQL Server released at the time of this writing. So it doesn't really matter if it currently compiles or not.


    I know this question is old by now, but perhaps someone coming across my post here might benefit from understanding why it's a big deal that TVPs can't be directly manipulated by a stored proc and read as output parameters by the calling client.

    "How do you..." questions regarding OUTPUT TVPs have littered SQL Server forums for more than half a decade now. Nearly every one of them involves someone attempting some supposed workaround that completely misses the point of the question in the first place.

    It is entirely non sequitur that you can "get a result set that matches a table type" by creating a Table Typed variable, Inserting into it and then returning a read from it. When you do that, the result set is still not a message. It is an ad hoc ResultSet that contains arbitrary columns that "just happen to match" a UDTT. What is needed is the ability for the following:

    create database [Test]
    create schema [Request]
    create schema [Response]
    create schema [Resources]
    create schema [Services]
    
    create schema [Metadata]
    
    create table [Resources].[Foo] ( [Value] [varchar](max) NOT NULL, [CreatedBy] [varchar](max) NOT NULL) ON [PRIMARY]
    
    insert into [Resources].[Foo] values("Bar", "kalanbates");
    
    create type [Request].[Message] AS TABLE([Value] [varchar](max) NOT NULL)
    create type [Response].[Message] AS TABLE([Resource] [varchar](max) NOT NULL, [Creator] [varchar](max) NOT NULL, [LastAccessedOn] [datetime] NOT NULL)
    
    create PROCEDURE [Services].[GetResources]
    (@request [Request].[Message] READONLY, @response [response].[Message] OUTPUT) 
    AS
    insert into @response
         select  [Resource].[Value] [Resource]
                ,[Resource].[CreatedBy] [Creator]
                ,GETDATE()  [LastAccessedOn]
         inner join @request as [Request] on [Resource].[Value] = [Request].[Value]
    GO
    

    and have an ADO.NET client be able to say:

    public IEnumerable GetResources(IEnumerable request)
    {
    using(SqlConnection connection = new SqlConnection("Server=blahdeblah;database=Test;notGoingToFillOutRestOfConnString")
    {
       connection.Open();
       using(SqlCommand command = connection.CreateCommand())
       {
          command.CommandText = "[Services].[GetResources]"
          command.CommandType = CommandType.StoredProcedure;
    
          SqlParameter _request;
          _request = command.Parameters.Add(new SqlParameter("@request","[request].[Message]");
          _request.Value = CreateRequest(request,_request.TypeName);
          _request.SqlDbType = SqlDbType.Structured;
    
          SqlParameter response = new SqlParameter("@response", "[response].[Message]"){Direction = ParameterDirection.Output};
          command.Parameters.Add(response);
    
          command.ExecuteNonQuery();
          return Materializer.Create>(response).AsEnumerable(); //or something to that effect.  
    
          //The point is, messages are sent to and received from the database.
          //The "result set" contained within response is not dynamic. It has a structure that can be *reliably* anticipated.
       }
    }
    }
    
    private static IEnumerable CreateRequest(IEnumerable values, string typeName)
    {
       //Optimally,
       //1)Call database stored procedure that executes a select against the information_schema to retrieve type metadata for "typeName", or something similar
       //2)Build out SqlDataRecord from returned MetaData
    
       //Suboptimally, hard code "[request].[Message]" metadata into a SqlMetaData collection
       //for example purposes.
       SqlMetaData[] metaData = new SqlMetaData[1];
       metaData[0] = new SqlMetaData("Value", SqlDbType.Varchar);
       SqlDataRecord record = new SqlDataRecord(metaData);
       foreach(string value in values)
       {
          record.SetString(0,value);
          yield return record;
       }
    
    }
    

    The point here is that with this structure, the Database defines [Response].[Message],[Request].[Message], and [Services].[GetResource] as its Service Interface. Calling clients interact with "GetResource" by sending a pre-determined message type and receive their response in a pre-determined message type. Of course it can be approximated with an XML output parameter, you can somewhat infer a pre-determined message type by instituting tribal requirements that retrieval stored procedures must insert its response into a local [Response].[Message] Table Typed variable and then select directly out of it to return its results. But none of those techniques are nearly as elegant as a structure where a stored procedure fills a response "envelope" provided by the client with its payload and sends it back.

提交回复
热议问题