Facing problem when looping through over list of objects - c#

public class PriceDetail
{
public int ID{ get; set; }
public string Package { get; set; }
public string Currency { get; set; }
public int Price { get; set; }
public string PackageType { get; set; }
public List<string> Items { get; set; }
}
[WebMethod]
public static string GetCardDetail(string category)
{
string cs = ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
List<PriceDetail> pricingCard = new List<PriceDetail>();
string query = "select [PricingID],[Package],[Currency],[Price],[PackageType] from [SeoDatabase].[dbo].[tbl_Pricing] where Category = #Category";
SqlConnection con = new SqlConnection(cs);
SqlCommand cmd = new SqlCommand(query, con);
cmd.Parameters.AddWithValue("#Category", category);
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
PriceDetail priceDetail = new PriceDetail();
priceDetail.ID = Convert.ToInt16(rdr["PricingID"]);
priceDetail.Package = rdr["Package"].ToString();
priceDetail.Currency = rdr["Currency"].ToString();
priceDetail.Price = Convert.ToInt16(rdr["Price"]);
priceDetail.PackageType = rdr["PackageType"].ToString();
pricingCard.Add(priceDetail);
}
con.Close();
List<string> items = new List<string>();
string queryList = "select [Item] from [SeoDatabase].[dbo].[tbl_PricingItem] where PricingID = #pricingID";
int i = 0;
while(i < pricingCard.Count)
{
SqlCommand listCmd = new SqlCommand(queryList, con);
con.Open();
listCmd.Parameters.AddWithValue("#pricingID", pricingCard[i].ID);
SqlDataReader listReader = listCmd.ExecuteReader();
while (listReader.Read())
{
items.Add(listReader["Item"].ToString());
}
pricingCard[i].Items = items;
items.Clear();
con.Close();
i++;
}
string obj = JsonConvert.SerializeObject(pricingCard);
return obj;
}
Every thing is going good when i reached on second loop while(i < pricingCard.Count) in this loop on 0 index its working properly and assigning right value to pricingCard[i].Items = items; problem is that when it goes for second index(1) it assigns the value correctly but also change the value of first index(0) and when loop moves for third time it changes the value of index(0).Items and index(1).Items with index(2).Items values
Please help me to solve

The reason this is not behaving as you might expect is that items, being of type List<string>, is a reference type, not a value type. This a concept that you will need to understand well whilst developing with managed languages such as C#. MSDN - Reference and Value
Simplistically , what you are saying by pricingCard[i].Items = items; is that pricingCard[i].Items is now a reference to items. That means, at any given point after, an evaluation of pricingCard[i].Items will just "refer" you to items, therefore any changes to items will reflect on all reference you have made to it.
What you need to do, at the being of each loop, is to "re-instantiating" items with items = new List<string>(). This is to say: create a new instance of the list, and new instance to with the next iteration of pricingCard[i].Items will refer.
while(i < pricingCard.Count)
{
items = new List<string>();
// Do the rest of your process
}
Alternatively, add the items directly:
while (listReader.Read())
{
pricingCard[i].Items.Add(listReader["Item"].ToString());
}
P.S.
Rather than using:
int i = 0;
while(i < pricingCard.Count)
{
i++;
}
Try:
for (int i = 0; i < pricingCard.Count; i++)
{}

Related

.net How to add Add to nested list object

I have the following class object
I fill a DataTable with data from my sp and the message from the DebtAllOut class
I roll through the DataTable and filling a list
public class DebtAllOut
{
public string message { get; set; }
public List<DebtAllDetail> debtalldetail { get; set; }
}
public class DebtAllDetail
{
public int debtid { get; set; }
public string name { get; set; }
}
var debtallout = new DebtAllOut();
da.SelectCommand.CommandType = CommandType.StoredProcedure;
da.SelectCommand.Parameters.AddWithValue("#ProfileID", profileid);
DataTable dt = new DataTable();
da.Fill(dt);
debtallout.message = "Providing general message";
List<DebtSingleOut> lstDebtSingleOut = new List<DebtSingleOut>();
if (dt.Rows.Count > 0)
{
for (int i = 0; i < dt.Rows.Count; i++)
{
DebtSingleOut debtsingleout = new DebtSingleOut();
if (dt.Rows[i]["RowID"] != DBNull.Value) {
debtsingleout.debtid = Convert.ToInt32(dt.Rows[i] ["RowID"]);
}
lstDebtSingleOut.Add(debtsingleout);
}
}
debtallout.debtalldetail.AddRange(lstDebtSingleOut);
The last line gives the the error. When I try to add the list to the debtalldetail nested class
debtallout.debtalldetail.AddRange(lstDebtSingleOut);
The error I receive is
Error CS1503 Argument 1: cannot convert from 'System.Collections.Generic.List<mystuff.Models.DebtSingleOut>' to 'System.Collections.Generic.IEnumerable<mystuff.Models.DebtAllDetail>'
You're trying to add a list lstDebtSingleOut of type List<DebtSingleOut> to the debtalldetail property which is of type List<DebtAllDetail>. It's not easy to understand full logic but I think you mixed up classes DebtAllDetail and DebtSingleOut.
You need to create a new DebtAllDetail object for each row and add it to the debtalldetail list or you can fix the code change the type to the necessary.
var debtallout = new DebtAllOut();
debtallout.message = "Providing general message;"
// inirialize new instance of List<DebtAllDetail> to add elements
debtallout.debtalldetail = new List<DebtAllDetail>();
da.SelectCommand.CommandType = CommandType.StoredProcedure;
da.SelectCommand.Parameters.AddWithValue("#ProfileID", profileid);
DataTable dt = new DataTable();
da.Fill(dt);
if (dt.Rows.Count > 0)
{
for (int i = 0; i < dt.Rows.Count; i++)
{
// create and initialize new DebtAllDetail object
DebtAllDetail debtAllDetail = new DebtAllDetail();
if (dt.Rows[i]["RowID"] != DBNull.Value)
debtAllDetail.debtid = Convert.ToInt32(dt.Rows[i]["RowID"]);
if (dt.Rows[i]["Name"] != DBNull.Value)
debtAllDetail.name = dt.Rows[i]["Name"].ToString(); // or another code to retrieve the name
// add initialized DebtAllDetail object to list
debtallout.debtalldetail.Add(debtAllDetail);
}
}
Given this class definition:
public class DebtAllOut
{
public string message { get; set; }
public List<DebtAllDetail> debtalldetail { get; set; }
}
It's important to understand the result of this line:
var debtallout = new DebtAllOut();
At this point, the value of debtallout.debtalldetail is still null!
This is because the class definition did not specify to actually create the list object. It only specified to create a variable that might (or might not) refer to some list variable. That is, you have a reference, but the reference doesn't refer to anything yet.
You can fix it in two ways. First, by changing the class definition:
public class DebtAllOut
{
public string message { get; set; }
public List<DebtAllDetail> debtalldetail { get; private set; } = new List<DebtAllDetail>();
}
(Notice the use of private. You will still be able to add or remove items from the list, because accessing members of the list, including methods like Add(), is still a get operation.)
The second fix is to manually create a new list instance:
var debtallout = new DebtAllOut();
debtallout.debtalldetail = new List<DebtAllDetail>();
But all that aside, we can significantly improve the code:
da.SelectCommand.CommandType = CommandType.StoredProcedure;
da.SelectCommand.Parameters.AddWithValue("#ProfileID", profileid);
DataTable dt = new DataTable();
da.Fill(dt);
var debtallout = new DebtAllOut() {
message = "Providing general message",
debtalldetail = new List<DebtAllDetail>()
};
for (int i = 0; i < dt.Rows.Count; i++)
{
if (dt.Rows[i]["RowID"] != DBNull.Value)
{
DebtSingleOut debtsingleout = new DebtSingleOut() {
debtsingleout.debtid = Convert.ToInt32(dt.Rows[i]["RowID"]);
}
debtallout.debtalldetail.Add(debtsingleout);
}
}

How to pass data, select for an array

How to save the select data below into an array.
SqlConnection conConexao1 = clsdb.AbreBanco();
SqlCommand cmd1 = new SqlCommand("select id, tamplete1, tamplete2 from usuarios ", conConexao1);
SqlDataReader dr1 = cmd1.ExecuteReader();
if (dr1.HasRows == true)
{
if (dr1.Read())
{
id = int.Parse(dr1[0].ToString());
templete1 = (dr1[1].ToString());
templete2 = (dr1[2].ToString());
}
}
I have already tried using foreach, but always passes the last table data.
As a collection, List provide better flexibility than array.
The collection should be created outside the loop and the element should be added inside the loop.
List<Usuarios> list = new List<Usuarios>();
using (SqlConnection conConexao1 = clsdb.AbreBanco())
using (SqlCommand cmd1 = new SqlCommand(
"select id, tamplete1, tamplete2 from usuarios ", conConexao1))
using (SqlDataReader dr1 = cmd1.ExecuteReader())
{
while (dr1.Read())
{
list.Add(new Usuarios
{
Id = dr1.GetInt32(0),
Templete1 = dr1[1].ToString(),
Templete2 = dr1[2].ToString()
});
}
}
The class to imitate your data structure
public class Usuarios
{
public int Id { get; set; }
public string Templete1 { get; set; }
public string Templete2 { get; set; }
}
if for some reason, you have to use an array as collection
Usuarios[] array = list.ToArray();

How to fetch Record in a faster manner from SQLDataReader In C#

we have a stored procedure , which results data as below.
testCol1 testCol2 testCol3 testCol4 testCol5
124 1234 4543 4532 5564
123 1235 4546 4537 5565
it has 190,000 records.
I am trying to fetch data in List<TestData> type and then pass it to third party.
below is the code:
public class TestData
{
public int testCol1 { get; set; }
public int testCol2 { get; set; }
public string testCol3 { get; set; }
public double? testCol4 { get; set; }
public int testCol5 { get; set; }
}
var inputs = new List<TestData>();
using (SqlConnection con = new SqlConnection(fitchConnectionString))
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = con;
cmd.CommandText = "dbo.ReadAll_ForTest";
cmd.CommandTimeout = 0;
con.Open();
using (SqlDataReader dr = new SqlDataReader(cmd.ExecuteReader()))
{
while (dr.Read())
{
inputs.Add(new TestData()
{
testCol1 = (int)dr["testCol1"];
testCol2 = (int)dr["testCol2"];
testCol3 =(string)dr["testCol3"];
testCol4 = (double)dr["testCol4"];
testCol5 = (int)dr["testCol5"];
});
}
}
}
//pass to third party
var output = thirdparty.Convert(inputs).ToArray();
its working fine , however is taking lots of time to fetch the data.
is there is a way we can fetch data in faster manner?
One way is to specify types explicitly, so that the framework doesn't have to figure out what you mean. Get the ordinals (indices) in advance, and extract the exact type from the column:
using (var dr = cmd.ExecuteReader())
{
var testCol1Idx = dr.GetOrdinal("testCol1");
var testCol2Idx = dr.GetOrdinal("testCol2");
var testCol3Idx = dr.GetOrdinal("testCol3");
var testCol4Idx = dr.GetOrdinal("testCol4");
var testCol5Idx = dr.GetOrdinal("testCol5");
while (dr.Read())
{
inputs.Add(new TestData()
{
testCol1 = dr.GetInt32(testCol1Idx);
testCol2 = dr.GetInt32(testCol2Idx);
testCol3 = dr.GetString(testCol3Idx);
testCol4 = dr.GetDouble(testCol4Idx);
testCol5 = dr.GetInt32(testCol5Idx);
});
}
}
Other than that, 100K+ are a lot of records. Do you really need all of them? Try to work with a subset of the data, or aggregate data before using them.

Insert lists of different types with foreach loop into a SQL Server database

I'm trying to add data into a database using a foreach loop to iterate through lists which contain values of different types.
List<string> productCodeList = new List<string>();
List<string> productNameList = new List<string>();
List<string> productPriceList = new List<string>();
List<int> competitorList = new List<int>();
List<DateTime> dateCreatedList = new List<DateTime>();
Is there a way to iterate through multiple lists with different types? Because at the moment I'm only able to insert one list to one column. What I want is to insert data to all the columns specified.
Or is there possibly a better way of doing this?
using (var con = new SqlConnection(connectionString))
{
con.Open();
using (var cmd = new SqlCommand(#"INSERT INTO OnlineProductsTemp$(CompetitorID, ProductCode, ProductName, Price, DateCreated)
VALUES(#CompetitorID, #ProductCode, #ProductName, #Price, #DateCreated)", con))
{
cmd.Parameters.Add("#CompetitorID", SqlDbType.Int);
cmd.Parameters.Add("#ProductCode", SqlDbType.VarChar);
cmd.Parameters.Add("#ProductName", SqlDbType.VarChar);
cmd.Parameters.Add("#Price", SqlDbType.Decimal);
cmd.Parameters.Add("#DateCreated", SqlDbType.DateTime);
foreach (var value in competitorList)
{
cmd.Parameters["#CompetitorID"].Value = value;
int rowsAffected = cmd.ExecuteNonQuery();
}
competitorList.Clear();
}
}
Thanks in advance!
You have logically related properties like name, code etc split into multiple lists which would be hard to maintain and error prone like #Santiago pointed out.
If you have complete control over the code, you may consider creating a Product class and having a collection of Products.
public class Product
{
public string Code { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CompetitorId { get; set; }
public DateTime DateCreated { get; set; }
}
public class DemoClass
{
public static void Demo()
{
var products = new List<Product>();
// fill the list here
using (var con = new SqlConnection(connectionString))
{
con.Open();
using (var cmd = new SqlCommand(#"INSERT INTO OnlineProductsTemp$(CompetitorID, ProductCode, ProductName, Price, DateCreated)
VALUES(#CompetitorID, #ProductCode, #ProductName, #Price, #DateCreated)", con))
{
cmd.Parameters.Add("#CompetitorID", SqlDbType.Int);
cmd.Parameters.Add("#ProductCode", SqlDbType.VarChar);
cmd.Parameters.Add("#ProductName", SqlDbType.VarChar);
cmd.Parameters.Add("#Price", SqlDbType.Decimal);
cmd.Parameters.Add("#DateCreated", SqlDbType.DateTime);
foreach (var p in products)
{
cmd.Parameters["#CompetitorID"].Value = p.CompetitorId;
cmd.Parameters["#ProductCode"].Value = p.Code;
cmd.Parameters["#ProductName"].Value = p.Name;
cmd.Parameters["#Price"].Value = p.Price;
cmd.Parameters["#DateCreated"].Value = p.DateCreated;
var rowsAffected = cmd.ExecuteNonQuery();
}
products.Clear();
}
}
}
}
I think you want something like this:
for (int i = 0; i < competitorList.Count; i++)
{
cmd.Parameters["#CompetitorID"].Value = competitorList[i];
cmd.Parameters["#ProductCode"].Value = productCodeList[i];
cmd.Parameters["#ProductName"].Value = productNameList[i];
// etc
int rowsAffected = cmd.ExecuteNonQuery();
}
Having them in a separate list like that, makes it really hard to identify which productCode goes with which productName.
I would suggest to build a dto to handle this. Something like:
public class Products
{
public string productCodeList {get; set;}
public string productNameList {get; set;}
public string productPriceList {get; set;}
public int competitorList {get; set;}
public DateTime dateCreatedList {get; set;}
}
than you can use:
List<Products> products = new List<Products>();
Alternatively you could just build the whole DataTable in your code and use SqlBulckCopy to insert it into your database.
DatTable
Quick Example of Datatables
SqlBulkCopy

Adding list in list

I have class SellStatement
public class SellStatement
{
public long billNo
public DateTime paymentDate;
public List<string> ProductName;
public List<double> quantity;
public List<double> ratePerQuantity;
}
When i am trying to access function GetSaleDetails
public Exception GetSaleDetails(List<SellStatement> lss1,string query)
{
try
{
for (int i = 0; i < lss1.ToArray().Length; i++)
{
query = "select * from [product details] where [bill no]=#billno";
com = new SqlCeCommand(query, con);
con.Open();
com.Parameters.AddWithValue("#billno",lss1[i].billNo);
sdr = com.ExecuteReader();
while (sdr.Read())
{
lss1[i].ProductName.Add(sdr.GetString(1));//Exception line
lss1[i].quantity.Add(sdr.GetDouble(2));
lss1[i].ratePerQuantity.Add(sdr.GetDouble(3));
}
}
con.Close();
return null;
}
catch (Exception e)
{
con.Close();
return e;
}
}
Null Reference Exception comes up atlss1[i].ProductName.Add(sdr.GetString(1));.I think error might be because of null value in at sdr.GetString(1) but i checked it has some value .My friend told me that you can't change Function argument value like this so i try to copy one list to other like this .
List<SellStatement> lss1 = new List<SellStatement>() ;
lss1.AddRange(lss);
But it doesn't help me out. I am not able to figure out what's wrong while adding element.
If you showed us your complete SellStatement class in the question, then the reason is clear:
You never initialized ProductName, quantity and ratePerQuantity. They are null and that's exactly what the exception is telling you.
To fix it, change your class to this:
public class SellStatement
{
public long billNo
public DateTime paymentDate;
public List<string> ProductName = new List<string>();
public List<double> quantity = new List<double>();
public List<double> ratePerQuantity = new List<double>();
}
Please note that this goes against the normal C# design guidelines that say you shouldn't have public fields. Consider redesigning your class like so:
public class SellStatement
{
List<string> _productName = new List<string>();
List<double> _quantity = new List<double>();
List<double> _ratePerQuantity = new List<double>();
public long billNo {get; set;}
public DateTime paymentDate {get; set;}
public List<string> ProductName { get { return _productName; } }
public List<double> quantity { get { return _quantity; } }
public List<double> ratePerQuantity { get { return _ratePerQuantity; } }
}

Categories