ASP.NET SqlDataReader and Lists - c#

I am getting data via a SqlDataReader and now looping through the results and putting the results in the list. I am trying to add 2 columns to each list, but I am unsuccessful.
Here is my code:
for (int i = 0; i < reader.FieldCount; i++)
{
List<string> costs = new List<string>();
if (reader.GetName(i).ToString().Contains("TotalCost"))
{
costs.Add(reader.GetValue(i).ToString());
}
if (reader.GetName(i).ToString().Contains("SqftCost"))
{
costs.Add(reader.GetValue(i).ToString());
}
jobList.Add(costs);
}
But this puts the two columns in separate lists, I really need the 2 columns in one list.
The reason I am doing it like this is because I have columns that are called TotalCost101, SqftCost101, TotalCost102, SqftCost102, TotalCost104, SqftCost104. So each column that contains TotalCost and SqftCost should be in its own list. I hope this makes sense, anyone got any ideas on how to put these 2 columns in their own list. So at the end I will have a bunch of lists with 2 values.
I updated my code so I now use a Class instead of a List
for (int i = 0; i < reader.FieldCount; i++)
{
CostMatrix costs = new CostMatrix();
if (reader.GetName(i).ToString().Contains("TotalCost"))
{
costs.TotalCost = reader.GetValue(i).ToString();
}
if (reader.GetName(i).ToString().Contains("SqftCost"))
{
costs.sqftCost = reader.GetValue(i).ToString();
}
jobList.Add(costs);
}
Here is the current output:
<d3p1:CostMatrix>
<d3p1:TotalCost>550</d3p1:TotalCost>
<d3p1:sqftCost i:nil="true"/>
</d3p1:CostMatrix>
<d3p1:CostMatrix>
<d3p1:TotalCost i:nil="true"/>
<d3p1:sqftCost>0.41</d3p1:sqftCost>
</d3p1:CostMatrix>
What I am looking for is:
<d3p1:CostMatrix>
<d3p1:TotalCost>550</d3p1:TotalCost>
<d3p1:sqftCost>0.41</d3p1:sqftCost>
</d3p1:CostMatrix>

Honestly, I would use an object.
public class Price
{
public decimal Sqft { get; set; }
public decimal Total { get; set; }
}
You have an object that actually represents something tangible. You're clearly indicating what type of price is applicable. This will avoid confusion with other people working on the project and for you, with an expressive usage. Nothing is being obfuscated into a Tuple or string.
Then when you use the data reader, you could do something along these lines:
public static T GetValueOrNull<T>(this IDataReader reader, string column)
{
int ordinal;
if(!string.IsNullOrEmpty(column) && !reader.IsDBNull(reader.GetOrdinal(column)))
if(int.TryParse(reader.GetOrdinal(column).ToString(), out ordinal))
return (T)reader.GetValue(ordinal);
return default(T);
}
You can basically tell this, "which column" then assign it to that property. This could also be handled by some form of object relational mapper.
// Example:
List<Product> products = db.GetProducts();
var example = products.Where(o => o.Price.Sqft >= 5.00);
var sample = products.Where(o => o.Price.Total <= 5.00);
You can store Price inside of another object, allowing a web-site to filter a product based on how multiple types of price values, for instance. It has other benefits as well, it will also document your code nicely, to know how pricing may be implemented.
Not search for a collection of strings, how would that persist throughout your application? A List<string> would be hard to find all implementations for price, unless seeking a data attribute. But these are a bunch of reasons.
Hope this clarifies a bit.
Based on your update, you could do:
public class CostMatrix
{
public ConcurrentList<decimal> Total { get; set; }
public ConcurrentList<decimal> Sqft {get; set; }
}
Your object would have two separate list, then as you read through the table column by column and row by row, you could simply add. So if you used the above static method it would be:
using(var connection = new SqlConnection(dbConnection))
using(var command = new SqlCommand(query, dbConnection))
using(var reader = new SqlDataReader())
while(reader.Read())
{
Total.Add(GetValueOrNull<decimal>(reader, "TotalCost");
Sqft.Add(GetValueOrNull<decimal>(reader, "Sqft1");
}
I placed ConcurrentList because your implementation may use async. Wasn't sure, if not you can use a normal List. But a List isn't thread safe by default, you'd need a ConcurrentList.

Related

How do I append the contents of a txt file into an array, but the txt file has different variable types

I'm not that great with coding and all since we've started learning the basics this year, and my groupmates aren't doing anything to help me, so I'm lost here and wanted to seek help from all the experts that are here.
I was wondering how I'd be able to sort the mixed data inside the Textbox properly
(I'm using the console.application to access windows.Forms because that's what our professor wants us to do) . As you can see in the image below, it gets arranged alphabetically instead of sorting the score in descending order with the names of the player beside their scores.
void show_Click(object sender, EventArgs e)
{
int counter = 0;
string line;
StreamReader TXT = new StreamReader("Leaderboard.txt");
List<string> list = new List<string>();
while ((line = TXT.ReadLine()) != null)
{
ListB1.Items.Add(line);
list.Add(line);
counter++;
}
string[] arr = list.ToArray();
Array.Sort(arr);
Array.Reverse(arr);
foreach (string item in arr)
{
ListB2.Items.Add(item);
}
this is barely a fraction of my whole code but this is the part I think is most essential to making my leaderboards function properly.
this is what my code results to, as of now...
Currently you're reading each line of the file as a single string. So there's no difference between "score" and "text", it's all just one text value.
It sounds like what you want is to parse that information into an object with two different properties. For example, suppose you have this class:
public class ScoreListing
{
public int Score { get; set; }
public string Text { get; set; }
}
Then your list would be something like:
List<ScoreListing> list = new List<ScoreListing>();
And to add to it, you'd need to parse information from line to create a new ScoreListing object. For example, it might look something like this:
while ((line = TXT.ReadLine()) != null)
{
var elements = line.Split(' | ');
var listing = new ScoreListing
{
Score = int.Parse(elements[0]),
Text = elements[1]
};
list.Add(listing);
counter++;
}
Once you have a populated List<ScoreListing> you can sort it with .OrderBy(), for example:
var sortedLeaderboard = list.OrderByDescending(l => l.Score);
There are multiple ways to approach the storage of the data, sorting the data, etc. But overall when you have a value like "123 | SomeText" then what you really have is a custom-serialized object with two distinct properties. Your first goal when reading this data into memory should be to de-serialize it into that meaningful object. Then you can easily use that object however you like.
By request, a bit more information on converting data back into a string...
The most object-oriented approach would be to override .ToString() on the class:
public class ScoreListing
{
public int Score { get; set; }
public string Text { get; set; }
public override string ToString()
{
return $"{Score} | {Text}";
}
}
Then you can add it to your list of strings:
foreach (var item in sortedLeaderboard)
{
ListB2.Items.Add(item.ToString());
}
Without overriding .ToString() you'd just manually perform the same operation:
foreach (var item in sortedLeaderboard)
{
ListB2.Items.Add($"{item.Score} | {item.Text}");
}

How to find the lowest ID in an array

I need to find the lowest customer id in the array and increase it by 1.
So I need the for cycle to search for the biggest id (0 by default) and increase by 1 every time ( create a new object of the customer class.
I really don't know how to implement the "i" value in order to make the cycle search for the id...
My model:
public class CustomerModel
{
public int IDCustomer {get; set;}
public string LastNameCustomer {get; set;}
public string FirstNameCustomer {get; set;}
public string AdressCustomer {get; set;}
}
My algorithm attempt:
CustomerModel[] MemoryCustomers = new CustomerModel[9];
OrderModel[] MemoryOrders = new OrderModel[9];
public string CreateCustomer(CustomerModel model)
{
CustomerModel NewCustomer = new CustomerModel();
for (int i = 0; i < 10; i++)
{
//What to put here
}
}
it is not a good practice to do that, you can use "SQL identity" if dealing with database.
or you can use type GUID to generate unique identifier for each customer.
Don't do that.
You should instead try to get the next id for a customer from some external provider. If you're using a database system, that can generate indices for you - you just need to define a counter or ID-generator or some such thing in the DBMS. How that is done depends on which DB you are using.
Otherwise, you could create your own method that you can call each time you need a new id; something like:
newCustomer.IDCustomer = GetNextId();
If doing that, make sure the method can never return any ID more than once, and you should be safe. There are several ways to do this; one is to constantly increase a number. Another is to use an UUID or GUID:
var id = Guid.NewGuid().ToString() // Generate a universally unique ID-string
The latter is especially useful if you want ID's that appear random, so that knowing one or two ID's won't make it easy to guess others (a common security problem / weakness in many apps).
Update:
Ok, ok: If you really want to simply get the next available ID as an int, based on existing int's, then try this:
private int GetNextId(){
// Create a list for easy handling with LINQ:
List<CustomerModel> customers = new List<CustomerModel)(MemoryCustomers);
// Select only ids, and only the highest:
int highestExistingId = customers
.Select(cust => cust.IDCustomer)
.Max();
return highestExistingId + 1;
}
If I've understood your question correctly now after re-reading it, I believe you want to update the lowest ID in your array, and replace it with a new customer,with the new ID - is that correct?
If so, try:
List<CustomerModel> customers = new List<CustomerModel)(MemoryCustomers);
var lowestId = customers.Select(cust => cust.IDCustomer).Min();
for (int i = 0; i < 10; i++)
{
// Identify the customer with that lowest id...
if(MemoryCustomers[i].IDCustomer == lowestId)
{
// ... replace him with the new one:
MemoryCustomers[i] = NewCustomer;
}
}
This may not be what you are searching for but it should work more consistent.
Add something like this to your Customer-Class:
private static int last_gen_id = -1;
public static int Gen_ID {
get {
last_gen_id++;
return last_gen_id;
}
}
And for every id, you want to generate, you just call Customer.Gen_ID and you got yourself a unique id. This will not work with threading, so be warned ;)

More efficient way to check DataTable values?

I want to iterate through my table, check to see if the Quantity received is higher than the quantity expected for each row, and if so execute some code.
The way I've done it below feels amateurish.
bool allRecieved = true;
foreach (DataRow row in SubVwr.Tables[0].Tbl.Rows)
{
if(row["QtyRcvd"] < row["QtyPer"]) allRecieved = false;
}
if(allRecieved) // execute code
You can use LINQ, for better readability (presuming those are int-columns):
bool allRecieved = SubVwr.Tables[0].Tbl.AsEnumerable()
.All(row => row.Field<int>("QtyRcvd") >= row.Field<int>("QtyPer"));
An advantage over your current loop is that this will stop as soon as one record doesn't match this condition. Your loop will continue until end without break.
This is a bit radical, but I'd start by not using DataTable, and instead use a simple model:
public class SomeType {
// I'd probably name these QuantityReceived etc, but... meh
public int QtyRcvd {get;set;}
public int QtyPer {get;set;}
// ... etc
}
Then I can very conveniently check properties etc. For example, to mirror Tim Schmelter's LINQ answer:
List<SomeType> someData = ...
var allReceived = someData.All(x => x.QtyRcvd >= x.QtyPer);
so now all we need is to load SomeType from the DB, which is where ORMs and micro-ORMs excel. For example, with "Dapper", this would be:
string region = "North"; // just to show parameter usage
var someData = connection.Query<SomeType>(
#"select * from SomeTable where Region=#region", new { region }).AsList();

How to choose the Data Stucture

I have a table of data like this.
I want to perform numerous operations on it like
How many times was PLAY 'no' when it was sunny
How many times was WINDY 'true' when PLAY was 'yes'
What data structure should I use?
right now I have simple array which is coming out to be one heck of a task to control.
string[,] trainingData = new string[noOfRecords,5];
using (TextReader reader = File.OpenText("input.txt"))
{
int i =0;
string text = "";
while ((text = reader.ReadLine()) != null)
{
string[] bits = text.Split(' ');
for (int j = 0; j < noOfColumnsOfData; j++)
{
trainingData[i, j] = bits[j];
}
i++;
}
}
To widen #Doan cuong's anwer,
I would use an enumarable list of objects.
each object can be calle: Record and the collection can be called Table.
(Table is IEnumarable).
Here is a simple example:
static void Main(string[] args)
{
Table table = new Table();
int count1 = table.records.Where(r => r.Play == false && r.Outlook.ToLower() == "sunny").Count();
}
public class Record
{
public bool Play;
public string Outlook;
}
public class Table
{
//This should be private and Table should be IEnumarable
public List<Record> records = new List<Record>();
}
this is a highly uncomfortable question because it's about opinion more then a valid programing question. a lot of programmers would say this or that. for the table you are showing there is no problem using array as you did for simple queries as you mentioned. for more complex data and queries i would suggest you'll take the time and effort to study LINQ
Create a class and write the values to properties. I.e.:
public class Weather
{
public string Outlook {get;set;}
...
}
And then store them into a List<Weather> collection (during your loop). Like already said, you can run LINQ queries on it. The internet is full of example how to use LINQ.

Why do the members of my list get overwritten with the last member of said list?

I am trying to write a program that prints out (in a string variable) the following information about an mdb database:
Table Name
Total number of columns of the table
List of columns as follows:
Column Name:
Column Data Type:
To accomplish this I used two custom types (public classes) and of course, lists. Here is the code I have so far (which by the way has been adjusted not in small part thanks to questions and answers gathered here):
Here are the classes I created to define the two new types I am using:
public class ClmnInfo
{
public string strColumnName { get; set; }
public string strColumnType { get; set; }
}
public class TblInfo
{
public string strTableName { get; set; }
public int intColumnsQty { get; set; }
public List<ClmnInfo> ColumnList { get; set; }
}
Here is the code that actually gets the data. Keep in mind that I am using OleDB to connect to the actual data and everything works fine, except for the problem I will describe below.
As a sample, I am currently testing this code with a simple 1 table db, containing 12 columns of type string save for 1 int32 (Long Int in Access).
//Here I declare and Initialize all relevant variables and Lists
TblInfo CurrentTableInfo = new TblInfo();
ClmnInfo CurrentColumnInfo = new ClmnInfo();
List<TblInfo> AllTablesInfo = new List<TblInfo>();
//This loop iterates through each table obtained and imported previously in the program
int i = 0;
foreach (DataTable dt in dtImportedTables.Tables)
{
CurrentTableInfo.strTableName = Globals.tblSchemaTable.Rows[i][2].ToString(); //Gets the name of the current table
CurrentTableInfo.intColumnsQty = dt.Columns.Count; //Gets the total number of columns in the current table
CurrentTableInfo.ColumnList = new List<ClmnInfo>(); //Initializes the list which will house all of the columns
//This loop iterates through each column in the current table
foreach (DataColumn dc in dt.Columns)
{
CurrentColumnInfo.ColumnName = dc.ColumnName; // Gets the current column name
CurrentColumnInfo.ColumnType = dc.DataType.Name; // Gets the current column data type
CurrentTableInfo.ColumnList.Add(CurrentColumnInfo); // adds the information just obtained as a member of the columns list contained in CurrentColumnInfo
}
//BAD INSTRUCTION FOLLOWS:
AllTablesInfo.Add(CurrentTableInfo); //This SHOULD add The collection of column_names and column_types in a "master" list containing the table name, the number of columns, and the list of columns
}
I debugged the code and watched all variables. It works great (the table name and column quantity gets registered correctly, as well as the list of column_names, column_types for that table), but when the "bad" instruction gets executed, the contents of AllTablesInfo are not at all what they should be.
The table name is correct, as well as the number of columns, and the columns list even has 12 members as it should have, but each member of the list is the same, namely the LAST column of the database I am examining. Can anyone explain to me why CurrentTableInfo gets overwritten in this manner when it is added to the AllTablesInfo list?
You're creating a single TblInfo object, and then changing the properties on each iteration. Your list contains lots of references to the same object. Just move this line:
TblInfo CurrentTableInfo = new TblInfo();
to the inside of the first loop, and this line:
ClmnInfo CurrentColumnInfo = new ClmnInfo();
inside the nested foreach loop, so that you're creating new instances on each iteration.
Next:
Important
Make sure you understand why it was failing before. Read my article on references if you're not sure how objects and references (and value types) work in C#
Use camelCased names instead of CamelCased ones for local variables
Consider using an object initializer for the ClmnInfo
Change your type names to avoid unnecessary abbreviation (TableInfo, ColumnInfo)
Change your property names to avoid pseudo-Hungarian notation, and make them PascalCased
Consider rewriting the whole thing as a LINQ query (relatively advanced)
The pre-LINQ changes would leave your code looking something like this:
List<TableInfo> tables = new List<TableInfo>();
int i = 0;
foreach (DataTable dt in dtImportedTables.Tables)
{
TableInfo table = new TableInfo
{
Name = Globals.tblSchemaTable.Rows[i][2].ToString(),
// Do you really need this? Won't it be the same as Columns.Count?
ColumnCount = dt.Columns.Count,
Columns = new List<ColumnInfo>()
};
foreach (DataColumn dc in dt.Columns)
{
table.Columns.Add(new ColumnInfo {
Name = dc.ColumnName,
Type = dc.DataType.Name
});
}
tables.Add(table);
// I assume you meant to include this?
i++;
}
With LINQ:
List<TableInfo> tables =
dtImportedTables.Tables.Zip(Globals.tblSchemaTable.Rows.AsEnumerable(),
(table, schemaRow) => new TableInfo {
Name = schemaRow[2].ToString(),
// Again, only if you really need it
ColumnCount = table.Columns.Count,
Columns = table.Columns.Select(column => new ColumnInfo {
Name = column.ColumnName,
Type = column.DataType.Name
}).ToList()
}
}).ToList();
You have only created one instance of TblInfo.
It's because you only have a single instance of TblInfo, which you keep updating in your loop and then add another reference to it to the List. Thus your list has many references to the same object in memory.
Move the creation of the CurrentTableInfo instance inside the for loop.

Categories