How do you pass a table valued parameter to a stored procedure with a computed column?
I have a table data type which I'm trying to pass a DataTable to a stored proc. I've got all my columns matching up in order and data type except for a computed column.
If I leave that column out of my DataTable I get this error:
System.Data.SqlClient.SqlException (0x80131904): Trying to pass a table-valued parameter with 3 column(s) where the corresponding user-defined table type requires 4 column(s).
If I include the column with null values I get this: System.Data.SqlClient.SqlException (0x80131904): The column "TransformationSum" cannot be modified because it is either a computed column or is the result of a UNION operator.
Haven't tried fiddling with any of the various properties of the particular DataColumn yet.
Table type definition:
CREATE TYPE [dbo].[DataPoint] AS TABLE
(
[Id] [int] NOT NULL,
[RawDataPoint] [decimal](18, 9) NULL,
[TransformedDataPoint] [decimal](18, 9) NULL,
[TransformationSum] AS ([RawDataPoint]-[TransformedDataPoint]),
PRIMARY KEY ([Id])
)
And the stored proc definition:
CREATE PROCEDURE [report].[spRptBubblePlot]
#pData [dbo].[DataPoint] READONLY,
....
AS
BEGIN
....
END
And my code which sets the parameter:
var parm = cmd.CreateParameter();
parm.ParameterName = "#pData";
parm.SqlDbType = SqlDbType.Structured;
parm.TypeName = "[dbo].[DataPoint]";
parm.Value = myDataTable;
cmd.Parameters.Add(parm);
(Note: everything works fine without the computed column.)
AFAIK, You can't modify table-valued parameters inside a stored procedure. If you need to perform a calculation and return it from within the proc, you need to pass the table-valued parameter without the column with null values, create a copy with an identical structure plus the extra column you need and return the new table with the extra column or as many extra columns you need.
Related
I search for a generic SQL script which can add rows in SQL tables and those added rows should contain minimum possible data: if a column is nullable the script should insert NULL, if not (NVARCHAR for instance) the minimum piece of data which is an empty string ''.
Example:
DECLARE #Test TABLE (
ColumnA INT NULL,
ColumnB BIT NOT NULL,
ColumnC NVARCHAR(MAX) NOT NULL,
ColumnD BIT NULL
)
INSERT INTO #Test (ColumnA, ColumnB, ColumnC, ColumnD)
VALUES (NULL, 0, '', NULL)
This ^ but a generic solution that would work for any table. If this is possible of course.
Use case / reason:
Found an inconsistency between a model and its table in a DB. The table has a BIT NULL column while the respective property is of bool type which cannot take nulls. Who knows how many of these are in the DB. I'd like to find such inconsistencies all at once by providing such "minimum data" rows, trying to load them in program's memory and catching NullReferenceException or InvalidCastException.
I am getting error
String or binary data would be truncated. The data for table-valued parameter doesn't conform to the table type of the parameter.The statement has been terminated.
Stored procedure is:
CreatePROCEDURE [dbo].[addquestion]
#dt as MyDataTable readonly
AS
BEGIN
insert into questiontbl(Question)
select(Question)
from #dt;
END
The table is:
CREATE TABLE [dbo].[questiontbl]
(
[checkval] [varchar](max) NULL,
[Question] [varchar](max) NULL
)
C# code:
con.Close();
con.Open();
DataTable sqa = Session["questionlist"] as DataTable;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#dt", sqa);
tvparam.SqlDbType = SqlDbType.Structured;
cmd.ExecuteNonQuery();
Cmd.ExecuteNonQuery() returns the error mentioned. I have matched the datatype - it is varchar(max) in type and table as well.
I have referred many url but didn't get proper solution for this.
The main reason for this issue is, we are not passing the data in the
specified length
But in our actual code we will be sent the valid data, but the value will not be passed and will through the mentioned issue.
Here the trick is,
While creating data table for the table valued parameter, we need to
create the column in the order we created in the table valued
parameter.
Please check the following code.
Solution (The following will work)
C#
DataTable users= new DataTable("Users");
users.Columns.Add("EmailAddress", typeof(string));
users.Columns.Add("Content", typeof(string));
DataTable data= users.NewRow();
data["EmailAddress"] = emailAddress;
data["Content"] = content;
Sql
CREATE TYPE [dbo].[ParamEulaEmailUser] AS TABLE(
[EmailAddress] [nvarchar](50) NOT NULL,
[Content] [nvarchar](max) NULL
)
The following will not work
c#
DataTable users= new DataTable("Users");
users.Columns.Add("Content", typeof(string));
users.Columns.Add("EmailAddress", typeof(string));
The reason is here while we sending data to the stored procedure, the table valued parameter takes the value in the given order and match with existing column in the order. So the content will be checked with the email address in the stored procedure and throw the following error
Error : String or binary data would be truncated. The data for table-valued parameter doesn't conform to the table type of the parameter
You have not posted the declaration for MyDataTable user-defined type, but you should increase the varchar size of the Question column in the MyDataTable definition.
DROP TYPE [dbo].[MyDataTable]
GO
CREATE TYPE [dbo].[MyDataTable] AS TABLE
(
[Question] [varchar](200) NULL --INCREASE THIS VALUE
)
Script a Drop and Create for the addquestion procedure, as well as the MyDataTable type.
Drop the stored proc, drop the MyDataTable type.
Edit the MyDataTable Create script as I mentioned, and run it, then create part for the stored proc.
The maximal length of the target column is shorter than the value you try to insert.
Rightclick the table in SQL manager and go to 'Design' to visualize your table structure and column definitions. increase column Length
We are building an MVC project that needs to make use of of the MVC DataGrid. As part of that, we are wanting to allow for filtering and ordering of the DataGrid columns. We want this to be handled on the Sql side, with paging. Handling the paging is really straightforward and we've already got that working with our Stored Procedures.
The challenge we are facing now is how to get what columns the user has sorted by, into the stored procedure so we can sort the records during paging. I played with using a Table Type to send in a 'collection' of columns using something like this:
CREATE TYPE [dbo].[SortableEntity] AS TABLE(
[TableName] [varchar](50) NULL,
[ColumnName] [varchar](50) NULL,
[Descending] [bit] NULL
)
CREATE PROCEDURE [dbo].[DoSomethingWithEmployees]
#SortOrder AS dbo.SortableEntity READONLY
AS
BEGIN
SELECT [ColumnName] FROM #SortOrder
END
We're using Dapper as our ORM, and we're constrained to using only Stored Procedures by policy. In my Repository, I use the following DataTable to try and insert the records into the SortableEntity which works fine.
var parameters = new DynamicParameters();
// Check if we have anything to sort by
IEnumerable<SortDefinition> sortingDefinitions = builder.GetSortDefinitions();
if (sortingDefinitions.Count() > 0)
{
var dt = new DataTable();
dt.Columns.Add(nameof(SortableEntity.TableName));
dt.Columns.Add(nameof(SortableEntity.ColumnName));
dt.Columns.Add(nameof(SortableEntity.IsDescending));
Type tableType = typeof(SortableEntity);
foreach(SortDefinition sortDefinition in sortingDefinitions)
{
var dataRow = dt.NewRow();
dataRow.SetField(0, sortDefinition.TableName);
dataRow.SetField(0, sortDefinition.Column);
dataRow.SetField(2, sortDefinition.IsDescending);
dt.Rows.Add(dataRow);
}
parameters.Add("SortOrder", dt.AsTableValuedParameter(tableType.Name));
}
With this I'm able to get my sorted values into the stored procedure, but I'm concerned with Sql Injection. One way I can see getting around it is to lookup in the sys-columns table to see if the columns given are valid columns before using them. I'm not sure how to go about doing that, and taking the valid columns and applying them to an order by statement in my Stored Procedure. Since we're not using Sql parameter objects for the values being inserted into the DataTable, how do we protect against Sql injection? I know using DynamicParameters will protect us for the values going into the Stored Procedure parameters, but how does that work when the value is a table containing values?
The biggest challenge though is the WHERE clause. We want to pass in a filter from the data grid into the stored procedure, so users can filter out results sets. The idea being that the stored procedure would filter, order and page for us. I know I can handle this easily in Dapper using embedded or dynamic Sql; attempting to handle this via a Stored Procedure has proven to be over-my-head. What would I need to do to have my Stored Procedure receive a predicate from the app, applicable to a series of columns, that it applies as a WHERE clause in a safe manor, that won't open us up to Sql Injection?
I guess the only way to make your parameter inputs 'safe' is to check the values before assigning to your stored proc parameters. You'd have to look for 'SELECT', 'DELETE', and 'UPDATE'. But, I think since you are working with column names instead of entire dynamic SQL commands, you should be ok. Read the following: tsql - how to prevent SQL injection
But, I'm no expert on this. You should do your own research.
To give you an idea on how to handle dynamic filtering in a stored procedure, I just use a SQL function that splits up a string with comma separated values and turns it into a table. I JOIN this function with the table that contains the column that needs to be filtered. For example, I need to filter my dataset with multiple values using the DIVISION column from some table. My stored procedure will take in a optional VARCHAR parameter of length 3000:
#strDIVISION VARCHAR(3000) = NULL
Next, when receiving a NULL value for this parameter, give it an empty string value:
SELECT #strDIVISION = ISNULL(#strDIVISION,'')
Instead of filtering in the WHERE clause, you can JOIN the string split function as such:
...
FROM tblTransDTL td
INNER JOIN tblTransHDR th ON th.JOB_ID = td.JOB_ID
INNER JOIN dbo.udf_STRSPLIT(#strDIVISION) d1 ON
(d1.Value = th.DIVISION OR 1=CASE #DIVISION WHEN '' THEN 1 ELSE 0 END)
The CASE statement helps to determine when all values should be allowed or use only the values from the parameter input.
Lastly, this is the SQL function that splits the string values into a table:
CREATE FUNCTION udf_STRSPLIT
(
#Delim_Values VARCHAR(8000)
)
RETURNS #Result TABLE(Value VARCHAR(2000))
AS
begin
WITH StrCTE(start, stop) AS
(
SELECT 1, CHARINDEX(',' , #Delim_Values )
UNION ALL
SELECT stop + 1, CHARINDEX(',' ,#Delim_Values , stop + 1)
FROM StrCTE
WHERE stop > 0
)
insert into #Result
SELECT SUBSTRING(#Delim_Values , start, CASE WHEN stop > 0 THEN stop-start ELSE 4000 END) AS stringValue
FROM StrCTE
return
end
GO
trying update Sql table form a DataTable via a Stored procedure i wanted to avoid multiple round trips on every row insert as i already have
a DataTable ready,
using Table-valued parameters
i can efficiently pass a whole table to SQL Server.
problem is that i need to update another table with the passed table
this is the original Row By Row insert
ALTER PROC [dbo].[InsertNewFILES]
#FileId int, #DriveL nchar(1), #PathToFolder nvarchar(300), #ContainingFolder nvarchar (100), #CurFileName nvarchar(100), #fileExt nchar(10), #fileSize int, #created smalldatetime
AS BEGIN
insert into [FileLookUps] output inserted.FileID, inserted.FName, #DriveL, #Filepath, #FolderName, #FileExt, #FileSize, #created into [HddFolderFiles]
values(#CurFileName,'')
so in the row by row version above [HddFolderFiles] accepts all Row-columns and [FileLookUps] takes inserted.FileID+Fname Columns
as the idea was to pass a table but how can i achieve same with a whole table is passed... is this doable ?
I'm in the point to implement an C# application that needs to consume already existing stored procedure that receive IDs or values in params.
My task in charge is in two steps:
1- migrate stored procedure in order to receive list of IDs(int) and list of current params, means like a table
2- implement the layer that cal this procedures and will receive List and KeyValuePair or KeyValuePair
What should be the best approach to do this ?
EntityFramework to wrap SPs or not ORM at alla?
How to implement List and KeyValuePair params ob SP side ? with Table-Valued params ?
I'm with SQL 2012
thanks,
Try in sql side User defined table type functionality and pass table as parameter in stored procedure.
For example:
CREATE TABLE Test
(
Id int NOT NULL IDENTITY (1, 1),
TestName varchar(50) NOT NULL,
Value int NULL
) ON [PRIMARY]
-- Create a table data type
CREATE TYPE [dbo].[TestType] As Table
(
--This type has structure similar to the DB table
TestName varchar(50) NOT NULL,
Value int NULL
)
--This is the Stored Procedure
CREATE PROCEDURE [dbo].[TestProcedure]
(
#Test As [dbo].[TestType] Readonly
)
AS
Begin
Insert Into Test(TestName,Value)
Select TestName, Value From #Test
End
C# code passing the data as follows:
DataTable dataTable = new DataTable("SampleDataType");
// We create column names as per the type in DB
dataTable.Columns.Add("TestName", typeof(string));
dataTable.Columns.Add("Value", typeof(Int32));
// And fill in some values
dataTable.Rows.Add("Metal", 99);
dataTable.Rows.Add("HG", null);
...
SqlParameter parameter = new SqlParameter();
// The parameter for the SP must be of SqlDbType.Structured
parameter.ParameterName="#Test";
parameter.SqlDbType = System.Data.SqlDbType.Structured;
parameter.Value = dataTable;
command.Parameters.Add(parameter);
I dealt with this same issue just recently. The links in the comments above lay out how to do SPs with table valued parameters. I've used the TVP method and it was easy and clean.
When it comes to Entity Framework, you can make EF aware of the Stored Procedures and call them and get the results back into EF Objects. Here's a link:
http://msdn.microsoft.com/en-us/data/gg699321.aspx
It's quite a bit more work than just calling the SPs with ADO. A major consideration is whether the results returned by the SP map directly onto one of your objects. Suppose you're joining a couple of tables in a search. You'd have to make a new model for those results and map the SP to that model and for what? So everything will run slower.
If you're just reading data and the results don't map exactly to an existing model you should skip EF and use ADO directly. If, OTOH, you're doing reads and writes and you really want to keep everything in EF for consistency's sake, it is possible.