Replace a List with an array - c#

I have the following method which I show below:
public static string loadLista(string pStrOp)
{
System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["WXYZ"].ConnectionString);
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("select distinct RTRIM(LTRIM(c.str_val)) as Id , RTRIM(LTRIM(c.str_val)) as Value " +
"from dbo.toc a " +
"inner join dbo.propval c on c.tocid = a.tocid and c.PROP_ID = 698 " +
"where a.pset_id = 114 and c.str_val is not null " +
"order by 2 asc;", conn);
var items = new List<Parametro>();
try
{
conn.Open();
using (var dr = cmd.ExecuteReader())
{
if (dr.HasRows)
{
int fc = dr.FieldCount;
var colums = new Dictionary<int, string>();
for (int i = 0; i < fc; i++)
colums.Add(i, dr.GetName(i));
object[] values = new object[fc];
while (dr.Read())
{
dr.GetValues(values); //Get All Values
Parametro item = Activator.CreateInstance<Parametro>();
var props = item.GetType().GetProperties();
foreach (var p in props)
{
foreach (var col in colums)
{
if (p.Name != col.Value) continue;
var value = values[col.Key];
if (value.GetType() != typeof(DBNull))
{
p.SetValue(item, value, null);
}
}
}
items.Add(item);
}
}
}
}
catch (Exception ex)
{
return new System.Web.Script.Serialization.JavaScriptSerializer().Serialize("");
}
return new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(items);
}
As you can see, I use a SqlCommand to make a query, then store it in a DataReader to process the data and finally save it in a list. The method works perfectly, the question is how can I replace the use of Lists in this method, to use arrays instead, since I have a limitation that prevents me from using Lists.
If you could give me an applied example, it would be very helpful. Thanks in advance for the help provided.

I don't quite understand why you cant use List<T> and you should figure that out first, however for academic purposes
Array.Resize Method (T[], Int32)
Changes the number of elements of a one-dimensional array to the
specified new size.
Remarks
This method allocates a new array with the specified size, copies
elements from the old array to the new one, and then replaces the old
array with the new one.array must be a one-dimensional array.
If array is null, this method creates a new array with the specified
size.
So instead of using a List<T>
Parametro[] items;
...
Array.Resize(items, (items?.Length ?? 0) +1 ) // resize
items[items.Length-1] = item; // add item to the last index
Note, this is fairly inefficient, List<T> actually works with a capacity that is set to a factor of 2 of whats needed when it has to internally increase the array.
Disclaimer : Not tested, there may be typos

Related

How to assign value in loop to parameters of stored procedure using SqlDataClient

I attempted to passing value from object params array to each parameter of stored procedure using loop, but I get this error
'Procedure or function Proc_GetPaging_Product has too many arguments specified.'.
The problem is cmd.Parameters already has parameters when using SqlCommandBuilder.DeriveParameters(cmd) so no need to add value but assigning.
public ServiceResult<T> GetPaging<T>(params object[] values)
{
var result = new ServiceResult<T>();
using(_conn)
{
_conn.Open();
using (SqlCommand cmd = _conn.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[SalesLT].[Proc_GetPaging_Product]";
SqlCommandBuilder.DeriveParameters(cmd);
int lengthParams = cmd.Parameters.Count;
for (int i = 1; i < lengthParams; i++)
{
cmd.Parameters.AddWithValue(cmd.Parameters[i].ParameterName, values[i - 1]);
}
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var item = Activator.CreateInstance<T>();
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
//Type convertTo = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
property.SetValue(item, reader[property.Name], null);
}
result.ListResult.Add(item);
}
reader.NextResult();
}
}
}
return result;
}
Error I got in debugger
You're adding the parameters twice.
Once in this row:
SqlCommandBuilder.DeriveParameters(cmd);
and once for each parameter in the loop:
for (int i = 1; i < lengthParams; i++)
{
cmd.Parameters.AddWithValue(cmd.Parameters[i].ParameterName, values[i - 1]);
}
Instead of using AddWithValue, simply do cmd.Parameter[i].Value = values[i].
Also, the parameters collection, like any other built-in collection in .Net, is zero based and not one based.
You loop should look like this:
for (int i = 0; i < lengthParams; i++)
{
cmd.Parameters[i].Value = values[i];
}
Also, I you should add a condition before the loop to make sure the number of values you have match the number of parameters the stored procedure have.
And one more note: you should consider setting the parameters in the code instead of using DeriveParameters: Official documentation clearly states:
DeriveParameters requires an additional call to the database to obtain the information. If the parameter information is known in advance, it is more efficient to populate the parameters collection by setting the information explicitly.

QueryMultiple Result Set Order Changed

I am executing a stored procedure using QueryMultiple to return multiple sets of data.
var gridReader = db.QueryMultiple("sp",
parameters,
commandType: CommandType.StoredProcedure);
I can very easily get each set given I know the order they will come back in.
SELECT * FROM dbo.Set1;
SELECT * FROM dbo.Set2;
SELECT * FROM dbo.Set3;
var set1 = gridReader.Read<Set1>();
var set2 = gridReader.Read<Set2>();
var set3 = gridReader.Read<Set3>();
However, I am in a situation where the order they will come back in may change. Another developer could decide to change the order for whatever reason. The stored procedure now becomes this:
SELECT * FROM dbo.Set1;
SELECT * FROM dbo.Set3;
SELECT * FROM dbo.Set2;
How can I handle this?
My initial attempt was to iterate each grid, checking the column names. This seemed to work well at first, but I wasn't able to figure out how to then project the grid into a class, besides manually setting each field. The main reason I'm using Dapper is so it can do this for me.
while (true)
{
var grid = gridReader.Read();
IDictionary<string, object> row = grid.FirstOrDefault();
if (row == null)
break;
if (row.Keys.Contains("Set1_UniqueColumnName"))
{
// Need something like grid.Read<Set1>();
}
else if (row.Keys.Contains("Set2_UniqueColumnName")) { }
else if (row.Keys.Contains("Set3_UniqueColumnName")) { }
}
My second idea was to read each grid into a class, check the unique fields of the class for nulls/default values, and trying the next class if the test failed. This obviously won't work though. .Read() will return the next grid of results. This solution would require me to be able to read the same grid over and over.
Dapper provides an IDataReader.GetRowParser extension method that enables type switching per row. From the Dapper docs here...
Usually you'll want to treat all rows from a given table as the same
data type. However, there are some circumstances where it's useful to
be able to parse different rows as different data types. This is where
IDataReader.GetRowParser comes in handy.
Imagine you have a database table named "Shapes" with the columns: Id,
Type, and Data, and you want to parse its rows into Circle, Square, or
Triangle objects based on the value of the Type column.
var shapes = new List<IShape>();
using (var reader = connection.ExecuteReader("select * from Shapes"))
{
// Generate a row parser for each type you expect.
// The generic type <IShape> is what the parser will return.
// The argument (typeof(*)) is the concrete type to parse.
var circleParser = reader.GetRowParser<IShape>(typeof(Circle));
var squareParser = reader.GetRowParser<IShape>(typeof(Square));
var triangleParser = reader.GetRowParser<IShape>(typeof(Triangle));
var typeColumnIndex = reader.GetOrdinal("Type");
while (reader.Read())
{
IShape shape;
var type = (ShapeType)reader.GetInt32(typeColumnIndex);
switch (type)
{
case ShapeType.Circle:
shape = circleParser(reader);
break;
case ShapeType.Square:
shape = squareParser(reader);
break;
case ShapeType.Triangle:
shape = triangleParser(reader);
break;
default:
throw new NotImplementedException();
}
shapes.Add(shape);
}
}
You'll need to get access to the IDataReader that the GridReader wraps or change your code to use the good old-fashioned ADO.NET SqlConnection & SqlCommand objects like this...
using (command = new SqlCommand("sp", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(parameters);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
// read row columns
}
}
}
Davmos's answer pointed me in the right direction. Needed to use a combination of ADO.NET and Dapper. Essentially use ADO.NET to retrieve and iterate through the data, but use Dapper to parse the rows into my objects. Note the use of FieldCount in the while loop in case a result set actually does return 0 rows. We want it to move on to the next result set, not break out of the loop.
Set1 set1 = null;
var set2 = new List<Set2>();
Set3 set3 = null;
using (var command = new SqlCommand("sp", conn))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(parameters);
command.Connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.FieldCount > 0)
{
var set1Parser = reader.GetRowParser<Set1>();
var set2Parser = reader.GetRowParser<Set2>();
var set3Parser = reader.GetRowParser<Set3>();
var isSet1 = HasColumn(reader, "Set1_UniqueColumnName");
var isSet2 = HasColumn(reader, "Set2_UniqueColumnName");
var isSet3 = HasColumn(reader, "Set3_UniqueColumnName");
while (reader.Read())
{
if (isSet1)
{
set1 = set1Parser(reader);
}
else if (isSet2)
{
set2.Add(set2Parser(reader));
}
else if (isSet3)
{
set3 = set3Parser(reader);
}
}
reader.NextResult();
}
}
}
public static bool HasColumn(IDataReader reader, string columnName)
{
for (var i = 0; i < reader.FieldCount; i++)
{
if (reader.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
return false;
}

how to separate the "collection" from creating and adding a new instance of "lookaheadRunInfo"

I am trying to "collect" the GetString(2) until GetString(0) changes,so am trying to find out how to separate the "collection" from creating and adding a new instance of "lookaheadRunInfo"?I have tried as below which throws an exception
System.NullReferenceException was unhandled by user code at line
lookaheadRunInfo.gerrits.Add(rdr.GetString(1)); ,can anyone provide guidance on how to fix this issue?
try
{
Console.WriteLine("Connecting to MySQL...");
conn.Open();
string sql = #"select lr.ec_job_link, cl.change_list ,lr.submitted_by, lr.submission_time,lr.lookahead_run_status
from lookahead_run as lr, lookahead_run_change_list as lrcl, change_list_details as cld,change_lists as cl
where lr.lookahead_run_status is null
and lr.submission_time is not null
and lrcl.lookahead_run_id = lr.lookahead_run_id
and cl.change_list_id = lrcl.change_list_id
and cl.change_list_id not in (select clcl.change_list_id from component_labels_change_lists as clcl)
and cld.change_list_id = lrcl.change_list_id
group by lr.lookahead_run_id, cl.change_list
order by lr.submission_time desc
limit 1000
";
MySqlCommand cmd = new MySqlCommand(sql, conn);
MySqlDataReader rdr = cmd.ExecuteReader();
var ECJoblink_previous ="";
var gerritList = new List<String>();
while (rdr.Read())
{
//Console.WriteLine(rdr[0] + " -- " + rdr[1]);
//Console.ReadLine();
var lookaheadRunInfo = new LookaheadRunInfo();
lookaheadRunInfo.ECJobLink = rdr.GetString(0);
if (ECJoblink_previous == lookaheadRunInfo.ECJobLink)
{
//Keep appending the list of gerrits until we get a new lookaheadRunInfo.ECJobLink
lookaheadRunInfo.gerrits.Add(rdr.GetString(1));
}
else
{
lookaheadRunInfo.gerrits = new List<string> { rdr.GetString(1) };
}
ECJoblink_previous = lookaheadRunInfo.ECJobLink;
lookaheadRunInfo.UserSubmitted = rdr.GetString(2);
lookaheadRunInfo.SubmittedTime = rdr.GetString(3).ToString();
lookaheadRunInfo.RunStatus = "null";
lookaheadRunInfo.ElapsedTime = (DateTime.UtcNow-rdr.GetDateTime(3)).ToString();
lookaheadRunsInfo.Add(lookaheadRunInfo);
}
rdr.Close();
}
catch
{
throw;
}
If I understand your requirements correctly, you wish to keep a single lookaheadRunInfo for several rows of the resultset, until GetString(0) changes. Is that right?
In that case you have some significant logic problems. The way it is written, even if we fix the null reference, you will get a new lookaheadRunInfo with each and every row.
Try this:
string ECJoblink_previous = null;
LookAheadRunInfo lookaheadRunInfo = null;
while (rdr.Read())
{
if (ECJoblink_previous != rdr.GetString(0)) //A new set of rows is starting
{
if (lookaheadRunInfo != null)
{
lookaheadRunsInfo.Add(lookaheadRunInfo); //Save the old group, if it exists
}
lookaheadRunInfo = new LookAheadRunInfo //Start a new group and initialize it
{
ECJobLink = rdr.GetString(0),
gerrits = new List<string>(),
UserSubmitted = rdr.GetString(2),
SubmittedTime = rdr.GetString(3).ToString(),
RunStatus = "null",
ElapsedTime = (DateTime.UtcNow-rdr.GetDateTime(3)).ToString()
}
}
lookahead.gerrits.Add(rdr.GetString(1)); //Add current row
ECJoblink_previous = rdr.GetString(0); //Keep track of column 0 for next iteration
}
if (lookaheadRunInfo != null)
{
lookaheadRunsInfo.Add(lookaheadRunInfo); //Save the last group, if there is one
}
The idea here is:
Start with a blank slate, nothing initialized
Monitor column 0. When it changes (as it will on the first row), save any old list and start a new one
Add to current list with each and every iteration
When done, save any remaining items in its own list. A null check is required in case the reader returned 0 rows.

How to load column data from DataReader?

I'm porting application to ASP.Net 5.0 with EF7 and found several problems. One of the issues is MS have dropped DataTable. I'm trying to transfer a bit of code to not use DataTable but read from SQLDataReader and record this into entities I have. I have to read data by columns, but looks like datareader can read only once.
The old code:
Series[] arrSeries = new Series[dt.Columns.Count - 1];
IList<Categories> arrCats = new List<Categories>();
Categories arrCat = new Categories();
foreach (DataColumn dc in dt.Columns)
{
var strarr = dt.Rows.Cast<DataRow>().Select(row => row[dc.Ordinal]).ToList();
if (dc.Ordinal == 0)
{
arrCat.category = strarr.Select(o => new Category { label = o.ToString() }).ToList();
}
else
{
Series s = new Series()
{
seriesname = dc.ColumnName,
renderas = null,
showvalues = false,
data = strarr.Select(o => new SeriesValue { value = o.ToString() }).ToList()
};
arrSeries[dc.Ordinal - 1] = s;
}
}
arrCats.Add(arrCat);
MultiFusionChart fusChart = new MultiFusionChart
{
chart = dictAtts,
categories = arrCats,
dataset = arrSeries
};
return fusChart;
The new code:
Series[] arrSeries = new Series[colColl.Count - 1];
IList<Categories> arrCats = new List<Categories>();
Categories arrCat = new Categories();
arrCat.category = new List<Category>();
for (int i = 0; i < reader.FieldCount; i++)
{
Series s = new Series()
{
seriesname = reader.GetName(i),
renderas = null,
showvalues = false
};
while (reader.Read())
{
if (i == 0)
{
Category cat = new Category();
cat.label = reader.GetValue(i).ToString();
arrCat.category.Add(cat);
}
else
{
SeriesValue sv = new SeriesValue();
sv.value = reader.GetValue(i).ToString();
s.data.Add(sv);
arrSeries[i - 1] = s;
}
}
}
arrCats.Add(arrCat);
MultiFusionChart fusChart = new MultiFusionChart
{
chart = dictAtts,
categories = arrCats,
dataset = arrSeries
};
return fusChart;
Where the code works, it returns null for Series. And I believe this is because reader went to the end while recording Categories. As far as I know it is not possible to reset DataReader?
Is there a way to load column data from DataReader to List? Or maybe there is other way how I can replace DataTable in this example?
You can read value from multiple columns by specifying its index like this
int totalColumns = reader.FieldCount;
for(int i=0;i<totalColumns;i++)
{
var label = reader.GetValue(i);
}
You can select value of all columns by looping through columns.
GetValue takes int as argument which is index of column not the index of row.
The problem is in your for loop. At first when your i is 0, You have initialized a 'Series` object S. After that,
while (reader.Read())
will read your entire Reader while your i will still be zero. SO only the
if(i == 0)
condition will return true and your entire reader will be spent. Afterwards, for i >= 1,
while (reader.Read())
will always return false keeping your array, arrSeries blank. Remove the while loop and directly read value from the dataReader using the index,
reader.GetValue(i);

How to Split Array that is Declared Global

I have an array that consist of First Name _ Last Name so they would read like so
Michael_Jordan
Javier_Lopez
George_Jones
I have an loop set-up to iterate through each of these, but I only want to take what's after the "" The problem I have is that the array was declared globally, and it is declared in far to many places for me to change. If I try to use the .Split function I receive an error of System.Array does not contain a definition for split. What is another option to take the data after the "" in the array?
public static string GetEmployees()
{
string queryString = "select employeeName from tbl_GlobalEmployeeData where state = 'AL';
SqlConnection connection = new SqlConnection(Connection.MyConnectionString.ConnectionStrings[0]);
{
SqlCommand cmd = new SqlCommand(queryString, connection);
connection.Open();
List<string> tempList = new List<string>();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
try
{
if (!reader.IsDBNull(0))
{
tempList.Add(reader[0].ToString() + "_" + reader[1].ToString());
}
}
catch
{
if (!reader.IsDBNull(0))
{
tempList.Add(reader[0].ToString() + "_" + reader[1].ToString());
}
}
}
reader.Close();
AllCompanyEmployees.State.ThisStore = tempList.ToArray();
for (int q = AllCompanyEmployees.State.ThisStore.GetLowerBound(0); q <= AllCompanyEmployees.State.ThisStore.GetUpperBound(0); q++)
{
return AllCompanyEmployees.State.ThisStore[q];
}
return null;
}
}
}
for (int q = AllCompanyEmployees.State.ThisStore.GetLowerBound(0); q <= AllCompanyEmployees.State.ThisStore.GetUpperBound(0); q++)
{
//This line is where I get the error mentioned above
string lastName = AllCompanyEmployees.State.ThisStore.Split('_')[1];
}
I think your question is "I want to split the array - So for example it reads Javier_Lopez I want to take Lopez from the array"
Very easy:
string last = yourString.Split(new char[] { '_' })[1];
Again, you seem to be using this on an array which is why you are getting that error. You need to iterate through your array and do this on each individual string in your array.
EDIT: To modify the array and leave only last names, try this:
int i = 0;
foreach (string s in stringArray)
{
stringArray[i] = stringArray[i].Split(new char[] { '_' })[1];
i++;
}
You can only use Split on strings. So you could do something like this:
List<string> lastNames = new List<string>();
for (int q = AllCompanyEmployees.State.ThisStore.GetLowerBound(0); q <= AllCompanyEmployees.State.ThisStore.GetUpperBound(0); q++)
{
string lastName = AllCompanyEmployees.State.ThisStore[q].Split('_')[1];
lastNames.Add(lastName);
}
At the end you would have a List<string> with all last names of your employees. With that you can continue to work.

Categories

Resources