2

I am working on a program in VB.Net that interfaces with a database using LINQ. Right now I am attempting to grab data from the following stored procedure on the database:

CREATE PROCEDURE dbo.GetTableColumn(
    @ColName VARCHAR(MAX),
    @TblName VARCHAR(MAX),
    @Result  BIT OUT
) AS
BEGIN
    IF (EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TblName AND COLUMN_NAME = @ColName))
        BEGIN
            DECLARE @SQL VARCHAR(MAX)
            SET @SQL = 'SELECT ' + @ColName + ' FROM ' + @TblName
            EXEC (@SQL)
            SET @Result = 1
        END
    ELSE
        SET @RESULT = 0
    RETURN @Result
END

The goal of the procedure, is that I can send a column name, and table name as variables, and if the column exists within the table the data from the table is returned.

I have added the procedure to my DataLinqContext, and this is the code that references the procedure:

Public Function GetTableColumn(ByVal col As String, ByVal table As String) As AutoCompleteStringCollection
    GetTableColumn = New AutoCompleteStringCollection
    Using dbContext As New Customer_LINQDataContext
        Dim result As Boolean
        Dim query = dbContext.GetTableColumn(col, table, result)
        MessageBox.Show(query.ToString())
    End Using
End Function

Now I put in the Message Box dialogue to test the output data. However every time the method runs, the result is always 1 or 0. I can never get the actual data I am trying to query for.

So what am I doing wrong here, and how do I fix it?

EDIT: I asked over on the Database Admin Stack Exchange to see if anyone there could offer any help. I got the following answer that can hopefully help, but I am still unsure how to fix the problem.

Your code is obtaining the result of the RETURN @result at the end of the stored proc. The results you want are being passed out of the stored procedure as a ResultSet. You need to use a DataReader object to see the rows.

I'm not sure how to do that using Linq. You'll hopefully get a better answer on your StackOverflow question.

Community
  • 1
  • 1
Skitzafreak
  • 1,797
  • 7
  • 32
  • 51
  • have you tried to execute this procedure in sql server without linq? – Marian Nasry Jul 19 '18 at 13:09
  • Yeah, it executes fine in SQL Server – Skitzafreak Jul 19 '18 at 13:12
  • try finding trace from SQL profiler for execution of this Stored proc to see if parameters are coming as expected. – Pranav Singh Jul 19 '18 at 13:14
  • @PranavSingh how do I do that? – Skitzafreak Jul 19 '18 at 13:17
  • just like Sql Server Management Studio, you have tool `Profiler` installed with SQL Server. Start Profiler & start trace & find stored procedure name for execution result of sp – Pranav Singh Jul 19 '18 at 13:23
  • @PranavSingh Are you saying the run the profiler in VisualStudio? – Skitzafreak Jul 19 '18 at 13:26
  • I am saying if Stored procedure manually execution is working fine with same table name & column name , make sure parameters values are passed correctly. You can check that by debugging or SQL server profiler trace – Pranav Singh Jul 19 '18 at 13:29
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176338/discussion-between-skitzafreak-and-pranav-singh). – Skitzafreak Jul 19 '18 at 13:33
  • is SQL injection a concern? – Ctznkane525 Jul 19 '18 at 13:38
  • @Ctznkane525 potentially, why? – Skitzafreak Jul 19 '18 at 13:39
  • you are dynamically creating a SQL statement...its not really parameterized in the traditional sense...its string building – Ctznkane525 Jul 19 '18 at 13:40
  • @Ctznkane525 what would your suggestion be then? – Skitzafreak Jul 19 '18 at 13:41
  • nm...the information schema exists will take care of the SQL injection problem – Ctznkane525 Jul 19 '18 at 13:48
  • @Ctznkane525 Not really, say you have someone who has the ability to add a column to a table but not drop a table, or they can do all of those things in their own schema, they could do something like this, which is totally legal: `CREATE TABLE theirschema.[*/ ]([1; DROP TABLE anything; /*]);` which would pass the `EXISTS` check and then yield a very dangerous `SELECT 1; DROP TABLE anything; /* FROM */ `. This is more dangerous in a procedure with elevated `EXECUTE AS` than as ad hoc, but worth noting in any case. – Aaron Bertrand Jul 19 '18 at 14:39
  • but that wont pass the information schema check @AaronBertrand...i dont really know what the point of the procedure is at all...but the information_schema check means itll get to the else statement – Ctznkane525 Jul 19 '18 at 14:57
  • @Ctznkane525 Sorry, but you didn't follow something along the way. The information_schema check **will pass**. https://i.stack.imgur.com/OfuYi.png – Aaron Bertrand Jul 19 '18 at 15:08
  • what's the exact ColName and TblName where that would work...noticing that the first call is parameterized @AaronBertrand...we're just arguing a technicality at this point...i dont see a point to this proc – Ctznkane525 Jul 19 '18 at 15:11
  • @Ctznkane525 It's a contrived scenario, which any user with enough privilege to add anything to the metadata could do. I'm not arguing about the point of the code in the question, I'm just disagreeing that the INFORMATION_SCHEMA checks "take care of" SQL injection. My example proves that they don't. – Aaron Bertrand Jul 19 '18 at 15:16
  • @AaronBertrand any user with enough privilege wouldnt need the procedure anyway to do damage to the database...you are adding extra variables to the situation – Ctznkane525 Jul 19 '18 at 15:18
  • @Ctznkane525 /shrug OK, but think ***beyond this question*** and consider the case where a procedure has elevated privileges (as many dynamic SQL solutions do) and the OP can't drop the tables they want to drop without it. This is a real concern. Again, think ***beyond this question*** and what your statement that SQL injection is "taken care of" may mean to people who *do* have those extra variables where it is definitely not "taken care of." This is a very popular site read by a lot of people far beyond the OP in this sandbox. – Aaron Bertrand Jul 19 '18 at 15:22
  • @Ctznkane525 [Did you look at my example?](https://i.stack.imgur.com/OfuYi.png) `<-- click` – Aaron Bertrand Jul 19 '18 at 15:42
  • @AaronBertrand - I might be missing the point here, but would deliberately passing the schema and performing an additional check on `SCHEMA_NAME` cover the example you make? I'm getting that you're thinking of some sort of shared database, where and end-user has legitimate rights to *another* schema, but has malicious intent towards *this* application's schema... – jimbobmcgee Jul 21 '18 at 04:06

2 Answers2

0

Stored procedures return code 0 is usually used when the sp has no error on the execution... For getting the recordset, I would void the 3rd parameter and return 1, if sp finds no table/column:

CREATE PROCEDURE dbo.GetTableColumn(
    @ColName VARCHAR(MAX),
    @TblName VARCHAR(MAX)
) AS
BEGIN
    IF (EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TblName AND COLUMN_NAME = @ColName))
        BEGIN
            DECLARE @SQL VARCHAR(MAX)
            SET @SQL = 'SELECT ' + @ColName + ' FROM ' + @TblName
            EXEC (@SQL)
        END
    ELSE
        RETURN 1
END

In your code... just use:

Dim query = dbContext.GetTableColumn(col, table)
AMG
  • 101
  • Thank you, but I am still having the problem. I need the procedure to give me the data in the specific table column. However, even with making this change to the procedure and code, I am only getting a value of `0` from running the procedure. – Skitzafreak Jul 19 '18 at 16:46
  • You said in SQL when running the SP. It works fine. If so... the problem seems to be the objects you are using in your code... try with this example to retrive the sp.... (C# and VB) https://msdn.microsoft.com/en-us/library/bb386975(v=vs.100)?cs-save-lang=1&cs-lang=vb#code-snippet-2 – AMG Jul 19 '18 at 17:30
0

So to get what I wanted I basically had to stop using Linq because I could not find any way to get that to work. I made a few modifications to the Procedure, and it now looks like so:

CREATE PROCEDURE dbo.GetTableColumn(
    @ColName VARCHAR(25),
    @TblName VARCHAR(25)
) AS
BEGIN
    IF (EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TblName AND COLUMN_NAME = @Col_Name))
        BEGIN
            DECLARE @SQL VARCHAR(MAX)
            SET @SQL = 'SELECT ' + @ColName + ' FROM ' + @TblName + ' WHERE ' + @ColName + ' IS NOT NULL'
            EXEC (@SQL)
        END
    ELSE
        RETURN 1
END

After that in my VB.Net program I added the following method:

Public Function ExecuteCMD(ByRef CMD As SqlCommand) As DataSet
    Dim DS As New DataSet()
    Try
        OpenDBConnection() 'Custom class to open the DB Connection'
        CMD.Connection = DB_CONNECTION 'Private member set to db connection on initialization'

        If CMD.CommandText.Contains(" ") Then
            CMD.CommandType = CommandType.Text
        Else
            CMD.CommandType = CommandType.StoredProcedure
        End If

        Dim adapter as New SqlDataAdapter(CMD)
        adapter.SelectCommand.CommandTimeout = 300

        adapter.Fill(DS)
        CloseDBConnection 'Custom class to close DB Connection'
    Catch ex As Exception
        Throw New Exception("Database Error: " & ex.Message)
    End Try
    Return DS
End Function

The credit for this method goes to Brian Webster here. It's a great bit of code that helped a lot with getting this to work. Finally I modified my GetTableColumn method to look like so:

Public Function GetTableColumn(ByVal col As String, ByVal table As String) As AutoCompleteStringCollection
    GetTableColumn = New AutoCompleteStringCollection
    Dim CMD As New SqlCommand("GetTableColumn")
    CMD.Parameters.Add("@ColName", SqlDbType.VarChar).Value = col
    CMD.Parameters.Add("@TblName", SqlDbType.VarChar).Value = table
    Dim DS As DataSet = ExecuteCMD(CMD)
    For Each DR As DataRow In DS.Tables(0).Rows
        If Not IsNothing(DR(0)) Then GetTableColumn.Add(CStr(DR(0)))
    Next
End Function

This setup gets me the results I want, and is not being a pain in my butt like LINQ has been, so I am posted this as a result in case others have the same problem as me. Ditch LINQ if it starts being frustrating, it isn't worth the time.

Skitzafreak
  • 1,797
  • 7
  • 32
  • 51
  • I don't get this `If CMD.CommandText.Contains(" ") Then`. You are saying if the `.CommandText` has a space in it. How can that determine whether the command is type `.Stored Procedure` or type `.Text`? – Mary Jul 19 '18 at 19:31
  • @Mary If I am writing a `SELECT` command, the `CommandText` would look like `'SELECT col FROM table'`. Detecting the space lets you know if it's a normal query as opposed to just calling a procedure – Skitzafreak Jul 19 '18 at 19:34
  • Got it! Thanks, I didn't think it through. – Mary Jul 19 '18 at 19:35
  • There is a great answer at https://stackoverflow.com/questions/51372427/using-like-and-in-a-stored-procedure-with-parameter-in-from-clause/51372665#comment89717836_51372665 from Dan Guzman, In the stored procedure he suggest the data type sysname for columns and tables. Then in the VB code the equivalent is SqlDbType is nvarchar, 128 – Mary Jul 19 '18 at 19:49
  • Does `Dim CMD As New SqlCommand("EXEC dbo.GetTableColumn")` not work? Or `Dim CMD As New SqlCommand("[Procedure with spaces]")`? I don't know if I would rely on a lack of a space to determine this must be a stored procedure call. – Aaron Bertrand Jul 24 '18 at 15:17