C# Casting item from SqlConnection to expose available items - c#

I have a SQL query which I'm calling directly in code using plain ADO.NET (not Entity Framework) due to the stored procedure containing a pivot table which can potentially return a variable number of columns depending on the parameter input.
I've got to the stage where I can get a list of row data from the query and while debugging, if I drill down deep enough on the base properties, I can see that the object contains the 2 properties that I need (_fieldNameLookup and _values).
I am trying to access these values but I am not sure how I'm meant to be casting the items to expose the properties that I need.
The C# query is as follows:
using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Services"].ToString()))
{
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = "[SPROCQUERY]";
cmd.CommandType = CommandType.StoredProcedure;
var queryData = cmd.ExecuteReader();
var crei = new List<Items>();
while (queryData.Read())
{
for (int i = 0; i < queryData.FieldCount; i++)
{
var x = queryData[i]; <-- I am trying to expose values in this object
}
}
queryData.Close();
return null;
}
Using intellisense on var x shows up with generic properties such as Equals, GetType, ToString.
Thanks

queryData is your DbDataReader. Referencing an index on the default property simply returns an object of the column number.
To find out the column name, use the GetName() method of queryData, and to get the type, use GetFieldType():
Type t = queryData.GetFieldType(i);
To reference properties of your object--you need to cast it to the appropriate type.

Related

UWP Custom Sqlite Query to JSON

I'm searching for a way to Execute Custom SQL Queries and to provide the result in JSON. Normally you have to provide a Class for the Query result e.g.
var query = dbConn.Query<ClassTypes>("Select a as key, b as value FROM table WHERE id = ?", new object[] { ObjectID });
But in my case, I don't know the SQL Statement, because its provided by an external JavaScript from a Webview.
This Webview might ask my application to Execute
Select a.col1 as foo,b.col1, a.col2 FROM table1 a INNER JOIN table2 b ON a.id=b.aid
And wants me to return:
foo:xxx
col2:yyy
Which columns are "asked" by the SQL Statement is completely free, or which aliases are used, I just want to execute the Statement an return key value pairs with the aliases or column names and the values in a JSON (for each row).
So I'm not able to prepare a custom Class for the Query, because I don't know the format of the SQL Query.
Does anyone have an idea?
I just want to execute the Statement an return key value pairs with the aliases or column names and the values in a JSON (for each row).
For your scenario, You could use SqlDataReader to approach, SqlDataReader contains GetName method that could use to get the column name as key, and it also contains GetSqlValue method that could retrieve column's value. If you can't confirm the field count, you could also use FieldCount to get current reader 's field counts
For example
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
if (conn.State == System.Data.ConnectionState.Open)
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = GetProductsQuery;
using (SqlDataReader reader = cmd.ExecuteReader())
{
var list = new List<Dictionary<string, object>>();
while (reader.Read())
{
var dict = new Dictionary<string, object>();
var i = 0;
do
{
var key = reader.GetName(i);
var value = reader.GetSqlValue(i);
dict.Add(key, value);
i++;
} while (i < reader.FieldCount);
list.Add(dict);
}
}
}
}
}
For more detail please refer this document.

How to get all the values in a row of a DB relation and assign each of them to a variable in ASP.NET C#

I'm trying to find a way to have access to all the values in a row.
The following code returns one cell. If I change select id to select *, I have access to the row but how can I break it apart?
string find_user = "select id from users where userName = '" + un + "'";
using (SqlConnection con = new SqlConnection(cs))
{
using (SqlCommand cmd = new SqlCommand(find_user, con))
{
con.Open();
user_id = cmd.ExecuteScalar().ToString();
/* use to pass the info to all the pages */
Session.Add("u_id", user_id);
}
}
You cannot access additional columns using .ExecuteScalar(), per the docs:
Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored.
Although it is not a route that I would recommend, you can iterate through the fields by using an index on a data reader:
SqlDataReader dataReader = cmd.ExecuteReader();
// for the query's result set, this while loop will go through all the records
while (dataReader.Read())
{
// for the current record, this for loop will go through all the fields
for (int i = 0; i < dataReader.FieldCount; i++)
{
var value = dataReader[i]; // do what you need with the data here
}
}
A better approach would be to specify the field names in the SQL query instead of using SELECT *, then get the values from the data reader by the specific field names (not relying on the order of the fields in the DB).
Also, you have a SQL injection vulnerability. You should look up what this means and how to parameterize a query.

How to select varying amount of columns with ExecuteStoreQuery

I'm dynamically building a select query with a varying number of columns.
select
a as col0,
b as col1,
...,
c as coln
...
The query is supposed to retrieve a matrix of integers.
When executing the query with ObjectContext.ExecuteStoreQuery, I get the right amount of lines but each line seems to be empty.
Here is my code:
var lines = context.ExecuteStoreQuery<List<int>>(queryString).AsEnumerable()
How could I make it work?
I found here that I should be using ADO.NET for this kind of things.
Unfortunately Entity Framework 6 does not have a lot of flexibility in the internal mapping code, so it is not able to map the SQL result to your List<int> or to any other collection of primitive types.
To understand why it is not able to do it, you need to know that internally EF6 uses a DbDataReader to read the SQL results, then it builds a ColumnMap from the expected generic result type (in your case the type is a generic List), and it performs a dynamic translation of the SQL results to a result object using the ColumnMap to know what column to map to what property of the object.
Per my explanation above, EF6 ExecuteStoreQuery method is trying to map your columns ("a", "b"..etc.) to the List object properties, and since there are no properties on the List class that match your SQL result column names it is not able to map it.
These limitations make ADO.NET one of the simplest option for dynamic columns. You can use a DbDataReader like in the following code:
var arr = new List<int>();
using (var md = new Context())
{
var conn = md.Database.Connection;
conn.Open();
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = "select Col1,Col2 from Entities";
using (var reader = (DbDataReader)cmd.ExecuteReader())
{
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
arr.Add(reader.GetInt32(i));
}
}
}
}
}

Convert C# SQL Loop to Linq

I have a list Called ListTypes that holds 10 types of products. Below the store procedure loops and gets every record with the product that is looping and it stores it in the list ListIds. This is killing my sql box since I have over 200 users executing this constantly all day.
I know is not a good architecture to loop a sql statement, but this the only way I made it work. Any ideas how I can make this without looping? Maybe a Linq statement, I never used Linq with this magnitude. Thank you.
protected void GetIds(string Type, string Sub)
{
LinkedIds.Clear();
using (SqlConnection cs = new SqlConnection(connstr))
{
for (int x = 0; x < ListTypes.Count; x++)
{
cs.Open();
SqlCommand select = new SqlCommand("spUI_LinkedIds", cs);
select.CommandType = System.Data.CommandType.StoredProcedure;
select.Parameters.AddWithValue("#Type", Type);
select.Parameters.AddWithValue("#Sub", Sub);
select.Parameters.AddWithValue("#TransId", ListTypes[x]);
SqlDataReader dr = select.ExecuteReader();
while (dr.Read())
{
ListIds.Add(Convert.ToInt32(dr["LinkedId"]));
}
cs.Close();
}
}
}
Not a full answer, but this wouldn't fit in a comment. You can at least update your existing code to be more efficient like this:
protected List<int> GetIds(string Type, string Sub, IEnumerable<int> types)
{
var result = new List<int>();
using (SqlConnection cs = new SqlConnection(connstr))
using (SqlCommand select = new SqlCommand("spUI_LinkedIds", cs))
{
select.CommandType = System.Data.CommandType.StoredProcedure;
//Don't use AddWithValue! Be explicit about your DB types
// I had to guess here. Replace with the actual types from your database
select.Parameters.Add("#Type", SqlDBType.VarChar, 10).Value = Type;
select.Parameters.Add("#Sub", SqlDbType.VarChar, 10).Value = Sub;
var TransID = select.Parameters.Add("#TransId", SqlDbType.Int);
cs.Open();
foreach(int type in types)
{
TransID.Value = type;
SqlDataReader dr = select.ExecuteReader();
while (dr.Read())
{
result.Add((int)dr["LinkedId"]);
}
}
}
return result;
}
Note that this way you only open and close the connection once. Normally in ADO.Net it's better to use a new connection and re-open it for each query. The exception is in a tight loop like this. Also, the only thing that changes inside the loop this way is the one parameter value. Finally, it's better to design methods that don't rely on other class state. This method no longer needs to know about the ListTypes and ListIds class variables, which makes it possible to (among other things) do better unit testing on the method.
Again, this isn't a full answer; it's just an incremental improvement. What you really need to do is write another stored procedure that accepts a table valued parameter, and build on the query from your existing stored procedure to JOIN with the table valued parameter, so that all of this will fit into a single SQL statement. But until you share your stored procedure code, this is about as much help as I can give you.
Besides the improvements others wrote.
You could insert your ID's into a temp table and then make one
SELECT * from WhatEverTable WHERE transid in (select transid from #tempTable)
On a MSSQL this works really fast.
When you're not using a MSSQL it could be possible that one great SQL-Select with joins is faster than a SELECT IN. You have to test these cases by your own on your DBMS.
According to your comment:
The idea is lets say I have a table and I have to get all records from the table that has this 10 types of products. How can I get all of this products? But this number is dynamic.
So... why use a stored procedure at all? Why not query the table?
//If [Type] and [Sub] arguments are external inputs - as in, they come from a user request or something - they should be sanitized. (remove or escape '\' and apostrophe signs)
//create connection
string queryTmpl = "SELECT LinkedId FROM [yourTable] WHERE [TYPE] = '{0}' AND [SUB] = '{1}' AND [TRANSID] IN ({2})";
string query = string.Format(queryTmpl, Type, Sub, string.Join(", ", ListTypes);
SqlCommand select = new SqlCommand(query, cs);
//and so forth
To use Linq-to-SQL you would need to map the table to a class. This would make the query simpler to perform.

OleDbParameters and Parameter Names

I have an SQL statement that I'm executing through OleDb, the statement is something like this:
INSERT INTO mytable (name, dept) VALUES (#name, #dept);
I'm adding parameters to the OleDbCommand like this:
OleDbCommand Command = new OleDbCommand();
Command.Connection = Connection;
OleDbParameter Parameter1 = new OleDbParameter();
Parameter1.OleDbType = OleDbType.VarChar;
Parameter1.ParamterName = "#name";
Parameter1.Value = "Bob";
OleDbParameter Parameter2 = new OleDbParameter();
Parameter2.OleDbType = OleDbType.VarChar;
Parameter2.ParamterName = "#dept";
Parameter2.Value = "ADept";
Command.Parameters.Add(Parameter1);
Command.Parameters.Add(Parameter2);
The problem I've got is, if I add the parameters to command the other way round, then the columns are populated with the wrong values (i.e. name is in the dept column and vice versa)
Command.Parameters.Add(Parameter2);
Command.Parameters.Add(Parameter1);
My question is, what is the point of the parameter names if parameters values are just inserted into the table in the order they are added command? The parameter names seems redundant?
The Problem is that OleDb (and Odbc too) does not support named parameters.
It only supports what's called positional parameters.
In other words: The name you give a parameter when adding it to the commands parameters list does not matter. It's only used internally by the OleDbCommand class so it can distinguish and reference the parameters.
What matters is the order in which you add the parameters to the list. It must be the same order as the parameters are referenced in the SQL statement via the question mark character (?).
But here is a solution that allows you to use named parameters in the SQL statement. It basically replaces all parameter references in the SQL statement with question marks and reorders the parameters list accordingly.
It works the same way for the OdbcCommand class, you just need to replace "OleDb" with "Odbc" in the code.
Use the code like this:
command.CommandText = "SELECT * FROM Contact WHERE FirstName = #FirstName";
command.Parameters.AddWithValue("#FirstName", "Mike");
command.ConvertNamedParametersToPositionalParameters();
And here is the code
public static class OleDbCommandExtensions
{
public static void ConvertNamedParametersToPositionalParameters(this OleDbCommand command)
{
//1. Find all occurrences of parameter references in the SQL statement (such as #MyParameter).
//2. Find the corresponding parameter in the commands parameters list.
//3. Add the found parameter to the newParameters list and replace the parameter reference in the SQL with a question mark (?).
//4. Replace the commands parameters list with the newParameters list.
var newParameters = new List<OleDbParameter>();
command.CommandText = Regex.Replace(command.CommandText, "(#\\w*)", match =>
{
var parameter = command.Parameters.OfType<OleDbParameter>().FirstOrDefault(a => a.ParameterName == match.Groups[1].Value);
if (parameter != null)
{
var parameterIndex = newParameters.Count;
var newParameter = command.CreateParameter();
newParameter.OleDbType = parameter.OleDbType;
newParameter.ParameterName = "#parameter" + parameterIndex.ToString();
newParameter.Value = parameter.Value;
newParameters.Add(newParameter);
}
return "?";
});
command.Parameters.Clear();
command.Parameters.AddRange(newParameters.ToArray());
}
}
Parameter NAMES are generic in the SQL support system (i.e. not OleDb specific). Pretty much ONLY OleDb / Odbc do NOT use them. They are there because OleDb is a specific implementation of the generic base classes.

Categories