Creating a key-value pair dynamically and then... and then - c#

Is it possible to create a key-value pair from an HTML radio button and text box value, then insert that pair into an array, then pass that array into a SQL Stored Procedure parameter that adds to the WHERE clause in the SP?
I am building a dashboard app that references one table in the SQL Server DB when it starts. This brings back the results of a simple select SP. I am pretty new to SQL so I am "psuedo-coding"(sorry)
DECLARE #SelectedDate NVARCHAR(50)
SELECT * FROM Tablename WHERE log_ts > #SelectedDate
However at this point I need to be able to select between 1-4 field names and give them a value to filter by.
I would love to set up those field names as a radio button, and to prevent injection, screen those inputs against known values in my application before sending them to the server, including inserting default values of the input is null.
My thought was to set up an array[string, string] once the screening is done, and pass that array into SQL as an array[field, value].(there would never be a null value).Here is what i would like the array to look like when being sent and all pairs are their default values:
QueryArray = ["application_name","Mobile"]
["login_id","*"]
["log_ts","Datetime.Today"]
["error_level","Exception"]
Then somehow pass it into the where clause like this:
SELECT *
FROM TableName
WHERE [application_name] = [Mobile]
AND [login_id]=[*]
AND [log_ts]=[Datetime.Today]
AND [error_level]=[Exception]
This is where my brain looks normally for a for-each or something, but SQL if pretty new to me... Any help would be greatly appreciated, even if its just a link to a previous article that I could not find...

It sounds like you want to use nullable parameters in conjunction with the coalesce operator. This works well if you have a known set of parameters, and then want to filter against them. So if you know all the possible fields you can search on, which is assuming you know ahead what fields you will display on your UI for people to filter with, then you can actually make a procedure like so:
create procedure [dbo].[MySampleProcedure]
#Property1Value <yourDataType> = null,
#Property2Value <yourDataType> = null,
....
as
begin
select
Col1,
Col2,
...
from
[dbo].[YourTable]
where
Column1Value = coalesce(#Property1Value, Column1Value)
and Column2Value = coalesce(#Property2Value, Column2Value)
....
end
This effectively means that if you omit certain parameters, you can still match based on the parameters you provide. When you receive your set from the UI, you can then match the pairs to parameters and you're off to the races.
If your field names are dynamic (not known ahead of time), then it becomes a more difficult issue. In that case, you will likely have to drop to dynamic SQL to accomplish what you want, and construct a query using parameterized SQL, to avoid injection attempts.

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

Can a ADO.NET SQL command parameter contain a sub-query?

Is it possible to create a SQL select max(id) as a variable inside a query?
This doesn't work:
command.CommandText = "INSERT INTO ordreid (ordrenr,ordreid) SELECT #ordrenr, #ordreid";
command.Parameters.AddWithValue("#ordrenr", nyordre);
command.Parameters.AddWithValue("#ordreid", ("MAX(ordreid)+1 FROM ordreid"));
command.ExecuteScalar();
Here is a photo of what I'd like to do. 5 customers have added items to their orders. Customer 1 has 3 items, customer 2 has 4 items, customer 3 has 1 item, and so on...
This way I have 1 ordreid even through the order could consist of 30 items.
Query parameters cannot include SQL verbatim.
This is how such prepared statements prevent SQL Injection as they are not directly inserted. Rather only the corresponding data is used in the query - in this case that is the string that contains SQL, and results in invalid SQL syntax.
The SQL text then needs to look similar to the following, although this probably does not do what is desired. (Asking how to do the higher level task will lead to actually useful queries/approaches.)
#"INSERT INTO ordreid (ordrenr, ordreid)
SELECT #ordrenr, MAX(ordreid)+1
FROM ordreid"
command.Parameters.AddWithValue("#ordreid", ("MAX(ordreid)+1 FROM ordreid"));
The method name AddWithValue hints at the reason why this won't work: Parameters contain data, that is: values. They cannot represent a part of a SQL statement.
Your intention appears to be that #ordreid should be replaced with that piece of SQL. If this is so, then there's no reason to even have a parameter. Simply perform the substitution manually and change the CommandText:
command.CommandText = #"INSERT INTO ordreid (ordrenr, ordreid)
VALUES (#ordrenr, (SELECT MAX(ordreid)+1 FROM ordreid));";
Note that I changed four things (apart from spreading the command text across two lines, for legibility's sake, using C#'s #"…" string syntax). Only the first two points are crucial:
I moved the MAX(…) SQL directly into your CommandText. This makes the (invalid) #ordreid parameter obsolete.
To determine the value of MAX(ordreid) FROM ordreid, you need a sub-query; thus the added SELECT before MAX. Otherwise, the syntax wouldn't be valid.
I replaced your SELECT with a VALUES table value constructor. (Otherwise, because of the previous point, we'd have two SELECTs very close to each other, which would look somewhat confusing.)
I added a ; at the end of your query. Current versions of SQL Server don't yet require such a statement terminator, but Microsoft has hinted that they might become compulsory in future versions of T-SQL. I therefore believe that it's a good habit to get into now.
That all being said, you should probably turn the ordreid column into an IDENTITY column and let SQL Server choose the value to be inserted (making the SELECT MAX(…) business obsolete). Otherwise, if two processes or threads execute the same INSERT command at the same time, you might end up with several rows having the same value for ordreid.

how to design search for multiple fields in asp.net

[Form]: ![Form to be Considered][Form]
[Form]:
I have these form from Access. Here, I am developing search application on asp.net. I have 2 heavy databases with same data structure in which one database include approx 12000 fields and another has approx. 9000 records. I want to do search records with any criteria say,
Dealer Number = 3123 and DLicenceNo = 3242314
Here, I am assuming that if user provides a field text, that only considered to be search, and others to be ignored.
Is there anyway to build query for this rather using long if clauses?
Probably needs some tweaking but, start by naming all your textbox controls your column names in your database
var conditionals = new Dictionary<string, string>();
foreach(Control c in Page.Controls)
{
if (c is TextBox)
{
if (!String.IsNullOrEmpty(c.Text))
conditionals.Add(c.Id, c.Text);
}
}
From there you could be VERY careful to build a query that only has the right where clauses based on your conditionals dictionary. Ideally you can make sure it's parameterized some how to avoid all worries of SQL Injection.
I use stored procedures, passing parameters default values, like this:
select field1,field2,... from table1 where
(dealer_number= #dealer_number or #dealer_number='0')
and (d_licence_no=#d_licence_no or #d_licence_no='0')
If you're not using some parameter for this search, just send its default value, and that criteria will be ignored.
you can use Sql Case statement ,they are easy to manage ,
query can be like
declare #SearchItem varchar(50);
declare #SearchValue int;
SELECT column1, column2
FROM table
WHERE
#SearchValue =
CASE #SearchItem
WHEN 'Dealer Number' THEN ''
WHEN 'DLicenceNo ' THEN ''
END
use the stored procedure with all search criterias as your sp arguments and pass it null when you doesn't want to apply any condition. sp will be as
Create procedure usp_search
(
#dealerNumber int=null,
#licenseNumber int=null
)
as
select * from table where
dealerNumber= isnull(#dealerNumber,dealerNumber)
and licenseNumber = isnull(#licenseNumber ,licenseNumber )

Is there any way to get the table hierarchy from a connection string in c#?

I have a current requirement to determine the table hierarchy from a sql statement within c#. For example, consider the following sql statement:
Select Table1.*, Table2.* from Table1
left join table2 on Table1.parentCol = Table2.childCol
That might return 7 columns, 3 for Table1 and 4 for table2. I need to know the column names, and ideally (though not mandatory) their types.
I have no control over what SQL Statement will be used, as this is a user entered field. In C# it's a very basic task to open a connection and create an SqlCommand using that statement. I have freedom to run the SQL into a SqlDataReader, or any other System.Data.SqlClient class if necessary, however I cannot find any combination that will return the columns, rather than the actual column values.
Is anyone able to help?
Many thanks and best regards
You cannot do what you are asking (easily).
More to the point, do not let users enter arbitrary TSQL (You will regret it at some point...).
Instead, create a 'Search' form that allows entering various params and use a parameterised query onto a view that joins all the tables/columns required.
There's no direct way. You'll need to parse names of all the tables from the sql query.
Once you have done that you'll need to write few queries on Information_Schema to get raw data for what you are looking for.
If you are on SQL Server, you may want to use Catalog View
ex-
Select * from sys.tables where [Name] = 'MyTable'

How to get the primary key from a table without making a second trip?

How would I get the primary key ID number from a Table without making a second trip to the database in LINQ To SQL?
Right now, I submit the data to a table, and make another trip to figure out what id was assigned to the new field (in an auto increment id field). I want to do this in LINQ To SQL and not in Raw SQL (I no longer use Raw SQL).
Also, second part of my question is: I am always careful to know the ID of a user that's online because I'd rather call their information in various tables using their ID as opposed to using a GUID or a username, which are all long strings. I do this because I think that SQL Server doing a numeric compare is much (?) more efficient than doing a username (string) or even a guid (very long string) compare. My questions is, am I more concerned than I should be? Is the difference worth always keeping the userid (int32) in say, session state?
#RedFilter provided some interesting/promising leads for the first question, because I am at this stage unable to try them, if anyone knows or can confirm these changes that he recommended in the comments section of his answer?
If you have a reference to the object, you can just use that reference and call the primary key after you call db.SubmitChanges(). The LINQ object will automatically update its (Identifier) primary key field to reflect the new one assigned to it via SQL Server.
Example (vb.net):
Dim db As New NorthwindDataContext
Dim prod As New Product
prod.ProductName = "cheese!"
db.Products.InsertOnSubmit(prod)
db.SubmitChanges()
MessageBox.Show(prod.ProductID)
You could probably include the above code in a function and return the ProductID (or equivalent primary key) and use it somewhere else.
EDIT: If you are not doing atomic updates, you could add each new product to a separate Collection and iterate through it after you call SubmitChanges. I wish LINQ provided a 'database sneak peek' like a dataset would.
Unless you are doing something out of the ordinary, you should not need to do anything extra to retrieve the primary key that is generated.
When you call SubmitChanges on your Linq-to-SQL datacontext, it automatically updates the primary key values for your objects.
Regarding your second question - there may be a small performance improvement by doing a scan on a numeric field as opposed to something like varchar() but you will see much better performance either way by ensuring that you have the correct columns in your database indexed. And, with SQL Server if you create a primary key using an identity column, it will by default have a clustered index over it.
Linq to SQL automatically sets the identity value of your class with the ID generated when you insert a new record. Just access the property. I don't know if it uses a separate query for this or not, having never used it, but it is not unusual for ORMs to require another query to get back the last inserted ID.
Two ways you can do this independent of Linq To SQL (that may work with it):
1) If you are using SQL Server 2005 or higher, you can use the OUTPUT clause:
Returns information from, or
expressions based on, each row
affected by an INSERT, UPDATE, or
DELETE statement. These results can be
returned to the processing application
for use in such things as confirmation
messages, archiving, and other such
application requirements.
Alternatively, results can be inserted
into a table or table variable.
2) Alternately, you can construct a batch INSERT statement like this:
insert into MyTable
(field1)
values
('xxx');
select scope_identity();
which works at least as far back as SQL Server 2000.
In T-SQL, you could use the OUTPUT clause, saying:
INSERT table (columns...)
OUTPUT inserted.ID
SELECT columns...
So if you can configure LINQ to use that construct for doing inserts, then you can probably get it back easily. But whether LINQ can get a value back from an insert, I'll let someone else answer that.
Calling a stored procedure from LINQ that returns the ID as an output parameter is probably the easiest approach.

Categories