DbDataAdapter with value from a db function - c#

is it possible to do a tableadapter update with a datarow[]
and for a datetime column the value is set to a db function ea getdate(), userDefinedFunctionReturnDateTime() etc..
so not fixed values from a datarow[]
INSERT INTO X(A,B,C)VALUES(1, '2015-01-01 00:00', 'name')
but function values
INSERT INTO X(A,B,C)VALUES(1, getdate(), 'name')
something like datarow.ItemArray[1] = "getdate()"; ?

Yes, just set the insert value based on the command your SQL database recognizes and it should work.
Ex: MySQL could use NOW()
SQL-Server could use GetDate()
all exactly as you have sampled. The only thing is, you would NOT be using a parameter value, just build your insert statement with that fixed context INSTEAD of an actual parameter place-holder.
Is there a specific way you are building your insert statements? or having them auto-generated somehow?

Related

Safest way to pass collection of values to Stored Procedure designating columns to order by and predicate to filter by

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

Send a empty DateTime value into SQL stored procedure. But not null

I have an SQL stored procedure which accepts a DateTime parameter which has a default value of NULL
#pmNext_Check_Date DATETIME=NULL
I want to use this parameter in 3 scenarios:
If it's NULL then don't update any records
If it's got a date value then update all my records specified in my WHERE clause
The problem one! Set all the date fields in my query to NULL for the records in my WHERE clause.
Here is the block of code within the SP that is causing me issues (the rest of the UPDATE statement is build elsewhere in the SP and works fine):
IF #pmNext_Check_Date IS NOT NULL
IF #pmNext_Check_Date ='' --This is the bit that is causing me a problem. I just need to check for a empty date
SET #sql = #sql + ' Next_Check_Date = NULL '
ELSE
SET #sql = #sql + ' Next_Check_Date = #pmNext_Check_Date '
SET #sql = #sql + ' WHERE ID IN (1, 2)'
So for example if I have the following 2 rows:
ID NextCheckDate
1 12/12/12
2 NULL
In scenario 1 I wouldn't pass the parameter in as the procedure will use the default value and no dates will be updated.
In scenario 2 I pass in a date value and update both rows with the date value
In scenario 3 I want to update the date value on my rows to be null. The difference between scenario 1 & 3 is in scenario 3 the user will be choosing to set the date values to null.
So, I wanted to pass a blank date into the stored procedure. I'm doing this from C# and would like to do something like the following:
SqlParameter param = new SqlParameter("#pmNext_Check_Date", "");
This fails as the SP is expecting a DateTime.
So I want to be able to pass in a blank date and also how do I check this within the SP. The current check which is below doesn't work:
IF #pmNext_Check_Date =''
Thanks in advance.
Hope this all makes sense.
I'm using C#4.0 and SQL 2008
There is no such thing as a "blank date". You could use a well-known sentinel value (01 Jan for some arbitrary ancient year, for example), but null would be preferable. Note that to pass an explicit null via a parameter, you need:
SqlParameter param = new SqlParameter("#pmNext_Check_Date", DBNull.Value);
If that doesn't have enough granularity, consider adding a separate boolean (bit) parameter (or similar) that clarifies what you want the sproc to do. Or: have multiple procs to do these different things.
One useful sentinel value for SQL Server is January 1, 1753 (SQL Server's minimum datetime value) - this can be produced in TSQL without string parsing as cast(-53690 as datetime).
You can't pass an empty string as a datetime. So, you have a couple options. You could add an additional parameter to indicate whether or not an update should occur. I suggest that as the best options for code readability and maintainability. The other option would be to pass in the parameter as a string and parse it. That way you could use your empty string concept.
Sql Server string to date conversion
Sorry, there is no way to do precisely what you're asking. A DATETIME value is either NULL or a valid date, there is no "empty" like there is with strings.
Another workaround is to pass a token value (that wouldn't be a valid date otherwise) to represent your so-called empty string, e.g. a common one I've seen used is 1900-01-01. Then you can differentiate in your stored procedure between NULL and "empty."
But I don't recommend doing this at all. I agree with the other suggestions: add another parameter and do the logic in a more meaningful way in the stored procedure.
If I can recall correctly, columns with a DATETIME datatype which allow NULLS
will default to the value 1900-01-01 instead of a BLANK value.
For example:
NULLS may be allowed for certain columns which might receive a value later down the line based on some kind of business logic. I have seen folks keep these open ended columns as varchar to enable a custom entry of some type or an empty string, something which datetime will not allow.
If you ask me I'd try not to mess around with the base column's data type and let it remain as DATETIME. For data retrieval and reporting purposes we might try the following approach, which may not be the best way to do it. But it works.
BEGIN
DECLARE #VarcharDateTable TABLE
([EndDate_Varchar] varchar(27))
BEGIN
INSERT INTO #VarcharDateTable([EndDate_Varchar])
SELECT
CONVERT(varchar(27), [EndDate_Datetime], 121)
FROM [dbo].[MainTable]
END
BEGIN
SELECT CASE WHEN [EndDate_Varchar] LIKE '1900-01-01%'
THEN 'Data unavailable'--or whatever you want
ELSE [EndDate_Varchar]
END AS [EndDate_Varchar] FROM #VarcharDateTable
END
END

Is there any way to use SCOPE_IDENTITY if using a multiple insert statement?

I will import many data rows from a csv file into a SQL Server database (through a web application). I need the auto generated id value back for the client.
If I do this in a loop, the performance is very bad (but I can use SCOPE_IDENTITY() without any problems).
A more performant solution would be a way like this:
INSERT INTO [MyTable]
VALUES ('1'), ('2'), ('3')
SELECT SCOPE_IDENTITY()
Is there any way to get all generated IDs and not only the last generated id?
No, SCOPE_IDENTITY() only gives you the one, latest inserted IDENTITY value. But you could check out the OUTPUT clause of SQL Server ....
DECLARE #IdentityTable TABLE (SomeKeyValue INT, NewIdentity INT)
INSERT INTO [MyTable]
OUTPUT Inserted.Keyvalue, Inserted.ID INTO #IdentityTable(SomeKeyValue, NewIdentity)
VALUES ('1'), ('2'), ('3')
Once you've run your INSERT statement, the table variable will hold "some key value" (for you, to identify the row) and the newly inserted ID values for each row inserted. Now go crazy with this! :-)

Insert DateTime from ASP.NET into Stored Proc

I am trying to insert a datetime stamp from an asp.net application into a db, but i keep recieving an error ::
The column "W_Date" cannot be modified
because it is either a computed column
or is the result of a UNION operator.
Can someone shed some light on this. Also, I automate the datetime in my asp in a label, then pull the text for the sql insert with the following code.
lblDate.Text = DateTime.Now.ToString();
Is this correct?
Below is my stored Proc code:
#date date
AS
BEGIN
INSERT INTO tbl_Wiki(W_Title, C_ID, W_Date)
VALUES (#title, #c_ID, GETDATE())
END
It doesn't sound like there's anything wrong with your ASP.NET code, assuming the W_Title is the correct column name. Do you have enough control over the schema to see if W_Title is a computed column or not?
Also, it looks like you're passing in "date" as a parameter to your proc, but you're not using it in the INSERT statement. If W_Date is a computed column to always be the current date, you should remove the W_Date paramter in your insert statement.
Check the definition of the column W_Date in table tbl_Wiki. It's probably a computed column, which means you can't modify it.
You could use this query to check if a column is computed:
select is_computed
from sys.columns
where object_id = object_id('tbl_Wiki')
and name = 'W_Date'
Example of a computed column:
create table Sample (a int, b int, c as sqrt(a*a+b+b))
Here, c is computed every time the row is retrieved. You can't overrule the calculation by specifying a value during an insert or update.

How do you get a NOW() value in C# using the MySQL Connector?

Is it possible in an easy way to get the NOW() timestamp from an UPDATE query? I'm trying to save the "lastupdated" value in the local cache, or is there in any way possible to get the exact MySQL server time which the update query was executed?
Best Regards; Görgen
To my knowledge, MySQL doesn't have functionality like Oracle's RETURNING or SQL Server's OUTPUT clause to be able to save a query by returning values from INSERT/UPDATE statements. So that means two statements minimum...
is there in any way possible to get the exact MySQL server time which the update query was executed?
The best I can think of is to define an audit column (they were standard approach at my previous work) for logging the timestamp when the record was updated. In MySQL, you can default the value so on update it is set to the timestamp value at that time:
ALTER TABLE your_table
ADD COLUMN update_timestamp TIMESTAMP NOT NULL DEFAULT ON UPDATE CURRENT_TIMESTAMP
...then this gives you a specific column value to query.
The usual way is to set a LastUpdated field in the database, either in the stored procedure or in a trigger. Then you can read it back.

Categories