SQL query result to Array - c#

I have written the following in an attempt to execute a sql query and store the result in an Array:
public static ArrayList DbQueryToArry()
{
string SqlCString = myConnString;
SqlConnection connection = null;
ArrayList valuesList = new ArrayList();
connection = new SqlConnection(SqlCString);
connection.Open();
SqlCommand command = new SqlCommand("Select CLIENTNO, ACCOUNT_Purpose from audit.ACCOUNTS_AUDIT", connection);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
valuesList.Add(Convert.ToInt32(reader[0].ToString()));
}
return valuesList;
}
But running the following
var myArray = DbQueryToArry();
Console.WriteLine(myArray.ToString());
Does not return the query result..

You will need to join them manually with string.Join or something similar:
Concatenates the elements of a specified array or the members of a
collection, using the specified separator between each element or
member.
Console.WriteLine(string.Join(",",myArray.ToArray()));
The reason why your version doesnt work is because, Console.Writeline has a bunch of overloads for different types, however it falls back to WriteLine(Object) when it can't find a specific resolution match.
The source code to WriteLine(Object value) is as follows (which can be found here).
public virtual void WriteLine(Object value) {
if (value==null) {
WriteLine();
}
else {
// Call WriteLine(value.ToString), not Write(Object), WriteLine().
// This makes calls to WriteLine(Object) atomic.
IFormattable f = value as IFormattable;
if (f != null)
WriteLine(f.ToString(null, FormatProvider));
else
WriteLine(value.ToString());
}
}
Notice how it calls value.ToString() ?
Object.ToString Method
Returns a string that represents the current object.
Remarks
Object.ToString is the major formatting method in the .NET Framework.
It converts an object to its string representation so that it is
suitable for display.
An ArrayList has no overload to ToString() that would be able to anticipate what you want to show, so it relies on the default.
The default Object.ToString() method
The default implementation of the ToString method returns the fully
qualified name of the type of the Object
Which brings me to my next point, don't use ArrayList, Use a generic array int[] or List<int> you will find it much more fun and rewarding (fun level 100)

Related

C# compiler error with explicit type vars sent to method with ref objects (converting from VB.net)

Long time programmer, new to C#. I am in the process of converting a solution from VB.net to C#. This particular function "getdata" returns values from the first row in a sql select. For this example I've simplified the code.
Due to the unknown datatypes being fetched from sql, the "getdata()" parms are objects. VB allows calling a function with any explicit datatype byref parms into objects, so I can send a string or int parm into an object and return it with no issues.
In C#, this method works for passing parms by value. Any type of byref (ref/in/out) the compiler arrors with "cannot convert from ref string to ref object"
What I've tried:
Changing all manner of parm ref/var type (in/out/ref/nothing)
Changing datatypes of variables from explicit to object works but produces lots of issues downstream. ie. I don't want to define everything as an object/I much prefer explicit datatypes.
explicit cast before calling using (object) before the variable name.
Changing everything to Dynamic types works but same issues as object.
My best solution, unfortunately, changes the functionality enough that it's going to cause issues with further solution conversion. I came up with returning the a/b/c variables as an anonymous object that are that set to the actual variables upon return to the calling function.
Is there any way that calling function parms can be explicitly typed and passed to an implicit data type like object?
If not, any better solution than returning anonymous type?
VB code -- working
Private Sub test()
Dim a$, b%, c$
getdata(1, a, b, c)
MsgBox($"a={a}, b={b}, c={c}")
Dim x As DateTime, y As String, z As String
getdata(2, x, y, z)
MsgBox($"x={x}, y={y}, z={z}")
End Sub
Private Sub getdata(opt As Integer, ByRef val0 As Object, ByRef Optional val1 As Object = Nothing, ByRef Optional val2 As Object = Nothing) As Boolean
'the real implementation of this function will accept sql string and return first row of data columns
'since fetched data will be of different types, parms are defined as objects
If opt = 1 Then
val0 = "Apples"
val1 = 2
val2 = "Oranges"
ElseIf opt = 2 Then
val0 = now
val1 = "Dogs"
val2 = "Cats"
End If
End Function
C# code -- compiler error -
I am hand converting VB code to help with the C# learning curve but my last ditch solution was to use a VB->C# converter which is produced here.
private void test()
{
string a = null;
int b = 0;
string c = null;
getdata(1, ref a, ref b, ref c); ************** error occurs here
MessageBox.Show($"a={a}, b={b}, c={c}"); "cannot convert from ref string to ref object"
DateTime x = default(DateTime);
string y = null;
string z = null;
getdata(2, ref x, ref y, ref z); ************** error occurs here
MessageBox.Show($"x={x}, y={y}, z={z}"); "cannot convert from ref string to ref object"
}
private bool getdata(int opt, ref object val0, ref object val1, ref object val2)
{
//real function will accept sql string and return first row of data columns
//since fetched data will be of different types, parms are defined as objects
if (opt == 1)
{
val0 = "Apples";
val1 = 2;
val2 = "Oranges";
}
else if (opt == 2)
{
val0 = DateTime.Now;
val1 = "Dogs";
val2 = "Cats";
}
return true;
}
There are some fundamental things in this method which make me believe you should spend more time refactoring vs direct translation. Restoring type safety is one of them (VB.Net made it easier to hide some poor type safety choices in a file where you have Option Strict Off for a couple modules), but this also REALLY scares me:
//real function will accept sql string
Functions like that tend to cause HUGE security problems, as well as other issues, especially when you also have a bunch of arguments for output values. If you're not well-versed in SQL Injection, NOW is the time to learn about it. You must also provide a way to include input data for the SQL command that is completely separate from the SQL string itself, or you'll eventually find yourself in big trouble.
This code needs some serious refactoring, not just simple conversion!
I suggest refactoring around a method like this:
public class DB
{
private static string ConnectionString {get;} = "connection string here";
private static IEnumerable<IDataRecord> getdata(string sql, Action<SqlParameterCollection> addParameters)
{
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
rdr.Close();
}
}
}
}
Notice the method is private; this is because we're not done building the class yet. Once you create this class, and remove the old getdata() method, everywhere that currently calls this method will turn into a compiler error. This is good; it gives you an easy way to find all those places you had poor code of this type.
So now we start looking at the new compiler errors. Each one will represent a place where you used to call getdata(). There's probably other code nearby to build up the SQL string. You want to move each of these sections to a new static method in the DB class.
One of those methods might look something like this:
public static IDataRecord MyNewDataMethod(int ID)
{
string SQL = "SELECT ... WHERE ID = #ID";
return getdata(SQL, p => {
p.Add("#ID", SqlDbType.Int).Value = ID;
}).FirstOrDefault();
}
But we can (and should) take this a step further. Typically, these results will represent objects of some type. After all, they had to come from a table, or at least a set of related tables. If you don't already have a class for each of these things, you probably should. These classes should have static methods named something like FromDataRecord(), which accept an IDataRecord or DataRow as input and return the class type as output. They are Factory methods. And now we update the methods to look more like this:
public static MyObjectType MyNewDataMethod(int MyObjectTypeID)
{
string SQL = "SELECT ... WHERE ID = #ID";
return getdata(SQL, p => {
p.Add("#ID", SqlDbType.Int).Value = MyObjectTypeID;
}).Select(MyObjectType.FromDataRecord).FirstOrDefault();
}
Here's another example that might return several records:
public static IEnumerable<MyObjectType> MyNewDataMethod(string SearchKey)
{
string SQL = "SELECT ... WHERE SearchColumn = #SearchKey + '%'";
return getdata(SQL, p => {
p.Add("#SearchKey", SqlDbType.NVarChar, 80).Value = SearchKey;
}).Select(MyObjectType.FromDataRecord);
}
If you find you have a lot of these methods, you can convert the private getdata() method to protected, put it in it's own class library project in the solution, and use separate public classes in the same project that can still access that method to divide the data access into logical areas.
I agree with Joel's sentiments; throw this code away rather than trying to salvage it. It's garbage.
If you add a reference to the Nuget package Dapper, your life will get a lot easier. With Dapper, you write SQL and it maps to objects for you. It looks something like this:
using(var c = new SqlConnection(connection_string_here){
var person = c.QueryFirst<(string Na, string Ad, int Ag)>("SELECT name, address, age FORM person WHERE id = #id", new { id = 123 });
}
There's a lot going on in this, so i'll unpack it:
The first line just creates a database conenction in a using, so it will be disposed of. You don't need to bother about anything else; Dapper will open the connection, use it, close it
The second line has some parts:
var person = - like Dim x = 1 in VB, var declares a variable that is type-detected by the compiler from whatever type is on the right hand side
c.QueryFirst<(string Na, string Ad, int Ag)> - QueryFirst is a Dapper extension method that runs a select query and pulls the first row. Dapper maps the query columns to the type you give in angle brackets. Here I've given a ValueTuple which is a way to get the C# compiler to "fake" a class for you based on the ValueTuple class. A discussion about how it works is a bit out of scope, but suffice to say when the compiler encounters (string X, string Y, int Z) it transforms behind the scenes into something that you can refer to as an object with those named/typed properties. Suffice to say, when all is done, you'll be able to say person.Na or person.Ad in your code
"SELECT name, address, age FORM person WHERE id = #id" - is a parameterized SQL. It looks up a person with some ID and pulls their data out in that order, name, address, age. The order in this case is important because AFIAWA dapper maps ValueTuples positionally, not by name. This is different to other things (example later) where it does map by name. The tuple has name/address/age, so the query pulls them in the same order
new { id = 123 } - is creating a C# anonymous type, a sort of internal-only compiler generated class (different to a valuetuple) that has no name, but does have a property called id with value 123. Dapper will scan your SQL string looking for parameters, and find one called #id, so it will pull the value 123 out of the supplied anonymous type's id property (name based this time, not positional)
If you have a class Person lying around, as you probably should if you're doing any reasonable amount of database-to-c#-and-back-again work, then the call can look like this:
class Person{
public string Name {get; set;}
public string Address {get; set;}
public int Age {get; set;}
}
...
c.QueryFirst<Person>("SELECT age, name, address FROM ... WHERE id = #i", new { i=123 });
This time we pass a full class Person - Dapper will map the proeprties by name, which is why they're in a different order in the SQL (it could even be SELECT * and dapper will just ignore the 10+ columns in our person table that arent represented by an class property) and it still works. If your SQL names don't match your class names, the simplest thing to do is alias them in the SQL:
c.QueryFirst<Person>("SELECT firstname+' '+lastname as name, ... FROM ... WHERE id = #i", new { i=123 });
I don't think there's an elegant solution - you can keep your 'getdata' method unchanged if you add extra baggage to every method call:
private void test()
{
string a = null;
int b = 0;
string c = null;
object temp_a = a;
object temp_b = b;
object temp_c = c;
getdata(1, ref temp_a, ref temp_b, ref temp_c);
a = (string)temp_a;
b = (int)temp_b;
c = (string)temp_c;
MessageBox.Show($"a={a}, b={b}, c={c}");
}
I ended up with the following
Hashtable gd = getData();
string location = (string)gd["location"];
int locationid = (int)gd["locationid"];
string frutata = (string)gd["frutata"];
where getData() just builds a hashtable of objects with the datareader columns.
My end goal was to create a simple callable function w/o a bunch of code to handle return values.
Dapper seems pretty cool and I will def. check that out.

Is there a way to detect if string concatenation have been used?

In our datalayer, I want to discourage the use of string concatenation and make people use parameters instead.
But as far as I know, there is no way to see if a parameter is a concatenated string.
I have no code example to show of detection, since I know of none.
var result = db.executeQuery("SELECT * FROM table WHERE id = " + id);
This is the kind of code I'd like to get rid of, either to replace with something like:
db.executeQuery($"SELECT * FROM table WHERE id = {id}");
or
db.executeQuery("SELECT * FROM table WHERE id = {0}", id);
Edit:
The command executeQuery is in our datalayer and handles parameters as SqlParameters, with types and values.
So in this case a SqlParameter called #id with type int would be created.
Regarding the FormattableString:
public T ExecuteObject<T>(FormattableString sql)
{
return executeSingleRow(sql.Format, sql.GetArguments()).ToType<T>();
}
Regarding the ExecuteQuery:
public int executeNonQuery(string sql, params object[] parameters)
{
var traceI = Traceadd(sql, parameters);
if (!open())
throw new Exception("Error executing query!", lastException);
try
{
command = Connection.CreateCommand();
command.CommandText = sql;
sql.SQLFormat(ref command, parameters);
var res = command.ExecuteNonQuery();
command.Parameters.Clear();
if (traceI != null)
traceI.Stop();
return res;
}
catch (Exception ex)
{
if (traceI != null)
traceI.Stop();
throw new DBException(command.CommandText, command.Parameters, ex);
}
}
If your executeQuery method only has a parameter of FormattableString, then you should be fine already - there's no conversion from string to FormattableString. For example:
using System;
class Program
{
static void Main(string[] args)
{
int id = 10;
ExecuteQuery("SELECT * FROM table WHERE id = " + id);
}
static void ExecuteQuery(FormattableString query)
{
}
}
That gives an error:
Test.cs(8,22): error CS1503: Argument 1: cannot convert from 'string' to 'System.FormattableString'
You just need to make sure that you don't have an overload of your method accepting string. The result of string concatenation is never a FormattableString. Indeed, I would strongly advise that you avoid ever overloading a method to accept FormattableString and string... there's no point in doing so if you're not going to change the behaviour, and if you are going to change the behaviour, that could be really confusing.
I'd personally consider changing to use an extension method on FormattableString though - something like:
public static SqlCommand ToCommand(
this FormattableString query,
SqlConnection connection)
{
// ...
}
That way you can separate the command creation from the execution... which means (aside from anything else) that you can then call ExecuteReader or ExecuteNonQuery without having any extra code yourself.

C# list in function param

I'm new to C#. I want to take a list as argument and return another from the data I get from the first one.
private List<DestinationGenericMapProps> ConstructDestinationMapPropsList(List<BoutiqueInWebService> datas)
{
var result = new List<DestinationGenericMapProps>(datas);
return result;
}
I get this error:
Error 241 The best overloaded method match for System.Collections.Generic.List<VDDataUpdaterGeneric.DataObjects.DestinationGenericMapProps>.List(int) has some invalid arguments
I know this is probably pretty basic but I'm new to C# and struggle with this. Thanks for your help.
List<BoutiqueInWebService> is not a List<DestinationGenericMapProps>.
This will not work unless BoutiqueInWebService is derived from DestinationGenericMapProps.
Basically, there is a List<T>(IEnumerable<T>) constructor, but the T's have to be the same.
Either change your return type to List<BoutiqueInWebService> and change your new statement:
private List<BoutiqueInWebService> ConstructDestinationMapPropsList(List<BoutiqueInWebService> datas)
{
var result = new List<BoutiqueInWebService>(datas);
return result;
}
or change your parameter to be of type List<DestinationGenericMapProps>:
private List<DestinationGenericMapProps> ConstructDestinationMapPropsList(List<DestinationGenericMapProps> datas)
{
var result = new List<DestinationGenericMapProps>(datas);
return result;
}
Alternatively, if you know how to make a DestinationGenericMapProps from a BoutiqueInWebService, you can use System.Linq and perform a select against the argument:
private List<DestinationGenericMapProps> ConstructDestinationMapPropsList(List<BoutiqueInWebService> datas)
{
var result = datas.Select(x => new DestinationGenericMapProps() { ... }).ToList();
return result;
}
Your method return type is a list of DestinationGenericMapProps, but you're trying to create list of BoutiqueInWebService (which is data).
You can do this to match your return type:
private List<DestinationGenericMapProps>
ConstructDestinationMapPropsList(List<BoutiqueInWebService> datas)
{
return (from d in datas
select new DestinationGenericMapProps()
{
// map properties here
Prop1 = d.SomePropInData
}).ToList();
}
You're getting the error because you're trying to populate a list of one type (DestinationGenericMapProps) with objects from a list of a different type (BoutiqueInWebService) which isn't type safe.
You can only do this if BoutiqueInWebService inherits from DestinationGenericMapProps.
C# supports function overloading, which means that a class can have more than one function with the same name as long as the parameters are different. The compiler decides which overload to call by compairing the types of the parameters. This applies to constructors too.
The List class has a three overloads of its constuctor:
List<T>()
List<T>(IEnumerable<T>)
List<T>(int)
I assume that you are trying to use the second of those as it will create a new list from the passed in one. For the list you are creating T is a DestinationGenericMapProps. So the constructors are:
List<DestinationGenericMapProps>()
List<DestinationGenericMapProps>(IEnumerable<DestinationGenericMapProps>)
List<DestinationGenericMapProps>(int)
The list you have passed in has T set to BoutiqueInWebService. As such the compiler is trying to find a constructor like this in the list above.
List<DestinationGenericMapProps>(IEnumerable<BoutiqueInWebService>)
As it can't find one it raises the error you have recieved.
Is it possible to cast a BoutiqueInWebService to a DestinationGenericMapProps object? If so you could do this:
var result = datas.Cast<DestinationGenericMapProps>().ToList()
If no direct cast is possible it may be possible to do a long hand cast like this:
var result = datas.Select(o => new DestinationGenericMapProps() { PropA = o.PropA, PropB = o.PropB /* etc */}).ToList();

Dapper strongly typed collection from Type

In my application I use Dapper to access the database.
I've the following example code:
public IEnumerable GetByParentId(Type childType, string table)
{
IDbConnection _connection = _dbProvider.GetConnection();
var _sqlString = "select * from " + table;
IEnumerable _ret = _connection.Query(_sqlString).ToList();
//return IEnumerable<Dapper.SqlMapper.FastExpando>
return _ret;
}
It is possible to cast FastExpando item to my childType or force Dapper to return a strongly typed collection?
I cannot change my method signature!
You can call Query method via reflection and supply generic TItem argument of your childType. Then, Dapper will return IEnumerable and you will be able to cast it.
Also, you could fork Dapper (it is not very big) and make a very simple overload of Query, that will include (Type childType) argument and call proper methods inside.
What you face is the problem of C# in working with generics. Being statically typed language, C# is bad a working with dynamic types. If you want to work dynamically, you always end up with reflection.
Here's a sample of how you could call query method with type arguments. You may have to correct this a bit:
public IEnumerable GetByParentId(Type childType, string table)
{
IDbConnection _connection = _dbProvider.GetConnection();
var _sqlString = "select * from " + table;
var t = typeof(SqlMapper);
var genericQuery = t.GetMethods().Where(x => x.Name == "Query" && x.GetGenericArguments().Length == 1).First(); // You can cache this object.
var concreteQuery = genericQuery.MakeGenericMethod(childType); // you can also keep a dictionary of these, for speed.
var _ret = (IEnumerable)concreteQuery.Invoke(null, new object[] { _connection, _sqlString });
return _ret;
}
Append:
Also, I see a more general design problem here. You want to specify type dynamically, but then want to get statically typed objects, that you will be able to cast (I assume statically, or you want to continue with reflection?). Then... Why do you create dynamic interface in the first place? You say, that you can't change the interface, but this looks a bit stupid. It seems, that all your context is statically typed, but then, for some reason, you have one dynamically typed method.
If you know types during compilation time (or via generic arguments in the runtime), then you should simply change your method to something like this:
public IEnumerable<T> GetByParentId<T>(string table)
{
IDbConnection _connection = _dbProvider.GetConnection();
var _sqlString = "select * from " + table;
var _ret = _connection.Query<T>(_sqlString);
return _ret;
}

casting problem from SqlDataReader

Suppose i have this sql statement and I have executed a sql command to get a datareader:
"select 1 union select 2"
//.....
var rdr = cmd.ExecuteReader();
and now i want to read the value in the first column of the first row:
var myInt = (int)rdr.GetValue(0); //great this works
var myLong = (long)rdr.GetValue(0); //throws cast exception, even though you can cast int to long
So it appears the type you cast to in C# needs to match exactly the SQL type. I.E. If the sql type is bigint, you can only cast to long. If the sql type is int, you can only cast to int. No mix and match...
I just want to get something that works regardless of the type of integer c# asks for and sql returns, as long as you could theoretically cast one to the other. So if SQL Server gives me a floating point type, and I'm asking for an int, I want the truncated int you get from doing that cast.
My goal is to make this work with generics, so I can have this function work when the generic parameter doesn't exactly match the datatype in sql server:
List<T> GetFirstColumn<T>(string sql) where T : struct
{
//get connection, execute reader
// loop:
// lst.Add( (T) rdr.GetValue(0));
}
I'd like this to work for both statments:
var sql1 = "Select 1"; //sql int
var sql2 = "Select cast(1 as bigint)"; //sql equivalent of a long
var lst1 = GetFirstColumn<int>(sql1);
var lst2 = GetFirstColumn<int>(sql2);
Does anyone have a relatively painless way of doing this?
Like Fredrik says, the value from SqlDataReader is boxed. You can convert a boxed value to an int with Convert.ToInt32, like:
int i = Convert.ToInt32(read[0]);
This will try to convert even if SQL Server returns a bigint or a decimal.
System.Convert will take care of the conversion.
T GetValue<T>(SqlDataReader rdr)
{
var dbVal = rdr.GetValue(0);
var csVal = (T)System.Convert.ChangeType(dbVal, typeof(T));
}
Caveat: if T == Nullable<S>, you need to do some extra work with reflection to get the underlying type and call ChangeType with typeof(S) as the type parameter. Apparently, MS didn't update the ChangeType function with .NET 2.0 and the introduction of nullables. And if it's a nullable, and dbVal is DBNull, you can just return null.
object dbValue = 5;
//this throws
Convert.ChangeType(dbValue, typeof(int?));
//this works
if(dbValue == DBNull.Value || dbValue == null)
{
if(typeof(int?).IsNullable) //or is a class, like string
{return null;}
dbValue = null;
}
var type = GetUnderlyingType<int?>(); //== typeof(int)
Convert.ChangeType(dbValue, type);
I think your problem is that GetValue returns an object. This means that in the case of an int, you will get an int boxed in an object. Then you cannot directly cast it to a long but must first unpack it as an int:
var myLong = (long)(int)rdr.GetValue(0);
This will be quite tricky using generics, I would say. Well, you could make generic methods with two type arguments; one specifying what type the field is, and one specifying the type you want. But I don't really see the need; SqlDataReader already has methods for the various data types, such as GetInt32, GetInt64 and so on, so the generic method would not really give any added value in that case.

Categories