Proper way to loop a SQL Query? - c#

I have a list that retrieves the student's grades by passing in a list of student class,
it then pulls the student grades by querying the student's name through the database. This code works fine however, when the studentList increases in size, this code gets really slow to execute,
What is the proper method for looping a list through a sql query?
private List<StudentClass> getStudentGrades(List<StudentClass> studentList)
{
for (int i =0; i < studentList.Count; i++)
{
string sqlcommand = "SELECT StudentGrades FROM Students WHERE StudentName=#StudentName";
conn.Open();
using (SqlCommand cmd = new SqlCommand(sqlcommand, conn))
{
cmd.Parameters.AddWithValue("#StudentName", studentList[i].StudentName);
cmd.ExecuteNonQuery();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Reader())
{
studentList[i].StudentGrades = int.Parse(reader["StudentGrades"].ToString());
}
}
}
return studentList;
}
public class StudentClass
{
public string StudentName {get; set; }
public int StudentGrades {get; set; }
}

First of all - you're unnecessary executing your command twice. You're calling ExecuteNonQuery first (it will has no effect since ExecuteNonQuery doesn't returns data, but it slows down execution since request actually being passed to SQL server and being executed). Then you're calling ExecuteReader from which you're actually retrieving data.
Second issue - you're executing new query for each student. So if you will have 1000 students in list - it will be 1000 queries executed.
Consider getting all data from database first, and then update your studentList accordingly.
Something like getting SELECT StudentName, StudentGrades FROM Students first, save result to some dictionary (or wherever you want) and then do your loop over studentList

private List<StudentClass> getStudentGrades(List<StudentClass> studentList)
{
string sqlcommand = "SELECT StudentGrades FROM Students WHERE StudentName=#StudentName";
var conn = new SqlConnection();
SqlCommand cmd = new SqlCommand(sqlcommand, conn);
cmd.Parameters.Add("#StudentName");
using (conn)
{
conn.Open();
for (int i = 0; i < studentList.Count; i++)
{
cmd.Parameters[0].Value= studentList[i].StudentName;
studentList[i].StudentGrades = (int)cmd.ExecuteScalar();
}
}
return studentList;
}
Note: You should retrieve the whole list in one query, it would be much much better.

As other users have pointed out, you don't need the executenonquery.
But the bigger issue is that your SQL statement only returns one student's data at a time. Is there another field you can use to select all students in a group at at time? I.e., is there a "class" field or something like that, so you can do something like:
select studentgrades
from students
where class = #class
If not, you can still write a kind of ugly sql statement with an in clause. So:
select studentgrades
from studens
where studentname in ('studentname1', 'studentname2', etc. )
Using that approach, you'd modify your code to be something like this:
private List<StudentClass> getStudentGrades(List<StudentClass> studentList)
{
string studentListParam = "";
foreach(StudentClass student in studentlist)
{
// blah blah stuff to only add comma if not first element
studentListParam = studentListParam + ", '" + student.StudentName + "';
}
string sqlcommand = #"
SELECT StudentName, StudentGrades
FROM Students
WHERE StudentName in (" + studentlist + ")";
conn.Open();
using (SqlCommand cmd = new SqlCommand(sqlcommand, conn))
{
cmd.Parameters.AddWithValue("#StudentName", studentList[i].StudentName);
cmd.ExecuteNonQuery();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Reader())
{
StudentClass student = studentList.Where(s => s.StudentName == string.Parse(reader["StudentName"]);
student.StudentGrades = int.Parse(reader["StudentGrades"].ToString());
}
}
}
return studentList;
}
public class StudentClass
{
public string StudentName {get; set; }
public int StudentGrades {get; set; }
}
Obviously you'd need some null checks and various other bits of error checking to make this really production ready, but that should be a starting point.
(Also, note that this approach doesn't try to parameterize the query, so you'd lost a little bit of parse time, but's going to be orders of magnitude less than you're losing with the multiple queries.)

Related

What changes may be made to my GetEmails() method to decrease database execution time?

The code is like this:
public class Email
{
public string MailAddress {get;set;}
public string MailOwner {get;set;}
public int MailSended {get;set;}
public int MailReceived {get;set;}
}
public class EmailList
{
public List<Email> Items {get; private set;}
private IDBConnection _connection;
public EmailList(IDBConnection connection)
{
_connection = connection;
}
public List<Email> GetEmails(int customerId)
{
var emailList = new List<Email>();
var sql = "SELECT * FROM EmailsAddress WHERE Id = '" + customerId + "'";
using (var ObjectToAccessDatabaseAndStoreResultsIntoDataTable = new ObjectToObtainDbResultsIntoDataTable(_connection, sql))
{
foreach(DataRow row in ObjectToAccessDatabaseAndStoreResultsIntoDataTable.Datatable)
{
var email = new Email{
MailAddress = Convert.ToString(row["MailAddress"]);
MailOwner = Convert.ToString(row["MailOwner"]);
MailSended = Convert.ToInt32(row["MailSended"]);
MailReceived = Convert.ToInt32(row["MailReceived"]);
}
emailList.Add(email);
}
return emailList;
}
}
When GetEmails() is called multiple times to retrieve mail addresses, for i.e. 2000 customers, it takes a very long time to return. Using SQL Server Profiler, I see that most of the time is for the GetEmails() routine.
Why is the GetEmails() routine taking so long? I am using SQL Server.
Does the Id-column in your EmailsAddress-table have an index on this column? If not, adding one will speed it up.
You always should use parametrized queries and not string concatination - it allows the database to optimize your queries and, even more important, protects you from sql injection attacks (although not that relevant in your case with an integer type).
var command =
new SqlCommand("SELECT * FROM EmailsAddress WHERE Id = #Id;", db);
command.Parameters.AddWithValue("#Id", yourTextValue);
command.ExecuteQuery();
What Patrick Hofman said, getting your customers mailaddresses by grouping the IDs or using a join when querying the customers will be way more efficient.
/Edit: Here an example for a query asking for multiple ids :
var command =
new SqlCommand("SELECT * FROM EmailsAddress WHERE Id IN (#Id1,#Id2,#Id3);", db);
command.Parameters.AddWithValue("#Id1", 1);
command.Parameters.AddWithValue("#Id2", 54);
command.Parameters.AddWithValue("#Id2", 96);
command.ExecuteReader();
Or, ignoring my own advice to use parameters :
var sql = "SELECT * FROM EmailsAddress WHERE Id IN (1,54,96)";
Example for doing this in a loop, with parameters :
var ids = new int[] { 1, 95, 46, 93, 98, 77 };
var isFirst = true;
var commandBuilder = new StringBuilder("SELECT * FROM EmailsAddress WHERE Id IN (");
var command = new SqlCommand("");
for (var i = 0;i<ids.Length;i++)
{
if (!isFirst) commandBuilder.Append(",");
else isFirst = false;
var paramName = "#Id" + i;
commandBuilder.Append(paramName);
command.Parameters.AddWithValue(paramName, ids[i]);
}
commandBuilder.Append(")");
command.CommandText = commandBuilder.ToString();
command.ExecuteReader();
// Read your result
Can you find where's the problem?
Yes, you are firing 2000 SQL statements to the database, which has to parse and execute them all one-by-one, while one SQL statement could suffice in this case.
What you could do, depending on the size of the database:
Load all records at once. Store them in a cache object, and answer from that cache. This can be a problem when there are a lot of rows and you only intend to get a small set of records;
Load multiple customers at once. This might help if you don't want to get the entire table at once, but do have a tight loop which repeatedly calls the GetEmails method. You could pass in a list of customer IDs and fetch those at once. This would require an in statement on your customerId.
You can get email address for list customers like this
public List<Email> GetEmails(List<int> listCustomerId)
{
var emailList = new List<Email>();
var sql = "SELECT * FROM EmailsAddress WHERE Id IN (" + string.Join(",", listCustomerId) + ")";
using ( var ObjectToAccessDatabaseAndStoreResultsIntoDataTable = new ObjectToObtainDbResultsIntoDataTable(_connection, sql))
{
foreach (DataRow row in ObjectToAccessDatabaseAndStoreResultsIntoDataTable.Datatable)
{
var email = new Email
{
MailAddress = Convert.ToString(row["MailAddress"]);
MailOwner = Convert.ToString(row["MailOwner"]);
MailSended = Convert.ToInt32(row["MailSended"]);
MailReceived = Convert.ToInt32(row["MailReceived"]);
}
emailList.Add(email);
}
return emailList;
}
}

Retrieve multiple items of data from database into one single label

I want to retrieve multiples values from database and display on one label.
My database design currently is:
courseid scholarshipid course
--------------------------------
1 1 maths
2 1 english
3 1 science
scholarshipid schName
-----------------------
1 moon
2 start
3 light
I would like to retrieve this information into one single label such as
Maths, English, Science
However, now I manage to retrieve one of it which is just maths.
This is my code in code behind:
protected void btnShowDetails_Click(object sender, EventArgs e)
{
string Selectedid = GVFBAcc.SelectedRow.Cells[1].Text;//get user id
int selectedIdtoPass = Convert.ToInt32(Selectedid);
courseBLL getRecord = new courseBLL();
addcourse courseRetrieve = new addcourse();
courseRetrieve = getRecord.getCourseDetail(selectedIdtoPass);
lbCourse.Text = courseRetrieve.course1 + "," + courseRetrieve.course1;
}
in my courseBLL
public addcourse getCourseDetail(int idcourse)
{
addcourse userObj = new addcourse();
courseDAL objtoPass = new courseDAL();
userObj = objtoPass.displayCourseInfo(idcourse);
return userObj;
}
in my DAL
public addcourse displayCourseInfo(int idcourse)
{
string strCommandText = "Select course from course where schokarshipid = #schokarshipid";
SqlCommand cmd = new SqlCommand(strCommandText, conn);
cmd.Parameters.AddWithValue("#scholarshipid", idcourse);
conn.Open();
SqlDataReader myReader = cmd.ExecuteReader();
addcourse userObj = new addcourse();
while (myReader.Read())
{
int sID = Convert.ToInt32(myReader["courseID"].ToString());
string course = myReader["course"].ToString();
userObj = new addcourse(sID, course);
}
myReader.Close();
return userObj;
}
Edited:
public addcourse displayCourseInfo(int idcourse)
{
var courses = new List<addcourse>();
string strCommandText = "Select statement here"
SqlCommand cmd = new SqlCommand(strCommandText, conn);
cmd.Parameters.AddWithValue("#scholarshipid", idcourse);
conn.Open();
SqlDataReader myReader = cmd.ExecuteReader();
while (myReader.Read())
{
int sID = Convert.ToInt32(myReader["scholarshipid"].ToString());
string course = myReader["course"].ToString();
courses.Add(new addcourse(sID,course));
}
myReader.Close();
return courses;
}
I have removed some fields for this purpose so that I will not confuse you
You should return a List<addcourse> from getCourseDetail() and from displayCourseInfo and then you can do this if your addCourse has Course property:
lbCourse.Text = string.Join(", ", courses.Select(x => x.Course));
If the property name is different then change x.Course to that.
So in your reader you will do this
var courses = new List<addcourse>();
while (myReader.Read())
{
int sID = Convert.ToInt32(myReader["courseID"].ToString());
string course = myReader["course"].ToString();
courses.Add( new addcourse(sID, course)) ;
}
return courses;
Your query is also wrong:
Select course, from course ss inner join where courseID =#courseID
So you should fix that and make sure you get the columns you need because right now it is only getting course column and it has syntax errors. Run your query directly against the db using management studio. Once your query works the copy paste it and replace where part with a parameter the way you have it right now.
EDIT
It seems you are still stuck with this so I will give you a more detailed answer; however, you should really study up on some fundamental programming concepts. Anyhow, here it is:
Your code behind:
protected void btnShowDetails_Click(object sender, EventArgs e)
{
string Selectedid = GVFBAcc.SelectedRow.Cells[1].Text;//get user id
// Make sure this is the scholarshipid or the whole thing will
// not work.
int selectedIdtoPass = Convert.ToInt32(Selectedid);
courseBLL courseRetriever = new courseBLL();
// Remember you will get a list here
List<addcourse> courses = courseRetriever.getCourseDetail(selectedIdtoPass);
// This will take the list and separate the course property with a comma and space
var courseNames = string.Join(", ", courses);
// Now assign it to the text property
lbCourse.Text = courseNames;
}
Your BLL:
// See here you are retuning a list
// But you need to pass the scholarshipid so you can get the courses for that scholarshipid
public List<addcourse> GerCourses(int scholarshipId)
{
courseDAL courseRetriever = new courseDAL();
// Get the list
List<addcourse> courses = courseRetriever.GetCourses(scholarshipId);
return courses;
}
Your DAL:
// See here you are retuning a list
// But you need to pass the scholarshipid so you can get the courses for that scholarshipid
public List<addcourse> GetCourses(int scholarshipId)
{
// Make sure your query is correct, see you have spelling mistakes
// it should be scholarshipid not schokarshipid. it has to match the column in your database
// Make sure your table is called course and it has 3 columns: 1. courseid 2. course
// 3. scholarshipid or this query will not work
string strCommandText = "Select courseid, course from course where scholarshipid = #scholarshipId";
SqlCommand cmd = new SqlCommand(strCommandText, conn);
cmd.Parameters.AddWithValue("#scholarshipid", scholarshipId);
conn.Open();
SqlDataReader myReader = cmd.ExecuteReader();
// create a list so we can hold all the courses
List<addcourse> userObjs = new List<addcourse>();
// This will read one row at a time so keep reading until there are no more records
while (myReader.Read())
{
int sID = Convert.ToInt32(myReader["courseid"].ToString());
string course = myReader["course"].ToString();
addcourse userObj = new addcourse(sID, course);
// add it to the list
userObjs.Add(userObj);
}
myReader.Close();
// return the list
return userObjs;
}
A few more tips:
Give your methods more meaningful names. See the names I gave them GetCourses, now it reads like English and it is easy to follow.
Your class addcourse should be called Course. addcourse sounds like an action "add course", that could be a good name for a method if you were adding courses. Also use Pascal notation for class names and method names. Use Camel casing for arguments, parameters and local variables.
Learn how to use the Visual Studio debugger. Go watch some YouTube videos on Visual Studio Debugger.
Create a for loop then add it on the variable you are using

The secure way to return some value from database (SQL SERVER & ASP.NET)

Is the code below implementing the secure way to retrieve the data from database?
help me please, I don't understand about SQL Injection. Someone told me this code can easily get injected. If yes, can somebody explain it? Thank you.
public int CheckID(string column, string table, string wheres)
{
int i = 0;
sqlcon = ConnectToMain();
string sqlquery = "SELECT "+column+" FROM "+table+" "+wheres+"";
using (sqlcon)
{
sqlcon.Open();
SqlCommand sqlcom = new SqlCommand(sqlquery, sqlcon);
using (sqlcom)
{
SqlDataReader dr = sqlcom.ExecuteReader();
dr.Read();
if (dr.HasRows)
{
i = dr.GetInt32(0);
}
else
{
i = 0;
}
}
sqlcon.Close();
}
return i;
}
This code has far too many problems.
Table, column and criteria are passed as strings and concatenated, which means that the code is prone to SQL injection.
Database details like table, column criteria are spilled into the function's caller. Are you going to use this method to query anything other than a Visitor table?
A reader is used when only a single value is wanted.
The connection is created outside the using block and stored in a field.
This is definitelly a memory leak and probably a connection leak as well. Just create the connection locally.
A simple command call fixes all of these problems:
public int CheckIDVisitor(visitorName)
{
string query = "SELECT ID FROM Visitors where Name=#name";
using (var sqlConn=new SqlConnection(Properties.Default.MyDbConnectionString))
using( var cmd=new SqlCommand(query,sqlConn))
{
var cmdParam=cmd.Parameters.Add("#name",SqlDbType.NVarChar,20);
cmdParam.Value=visitorName;
sqlConn.Open();
var result=(int?)cmd.ExecuteScalar();
return result??0;
}
}
You could also create the command in advance and store it in a field. You can attach the connection to the command each time you want to execute it:
public void InitVisitorCommand()
{
string query = "SELECT ID FROM Visitors where Name=#name";
var cmd=new SqlCommand(query,sqlConn);
var cmdParam=cmd.Parameters.Add("#name",SqlDbType.NVarChar,20);
_myVisitorCommand=cmd;
}
...
public int CheckIDVisitor(visitorName)
{
using (var sqlConn=new SqlConnection(Properties.Default.MyDbConnectionString))
{
_myVisitorCommand.Parameters.["#name"]Value=visitorName;
_myVisitorCommand.Connection=sqlConn;
sqlConn.Open();
var result=(int?)cmd.ExecuteScalar();
return result??0;
}
}
An even better option would be to use a micro-ORM like Dapper.Net to get rid of all this code:
public int CheckIDVisitor(visitorName)
{
using (var sqlConn=new SqlConnection(Properties.Default.MyDbConnectionString))
{
string sql = "SELECT ID FROM Visitors WHERE name=#name"
var result = conn.Query<int?>(sql, new { name = visitorName);
return result??0;
}
}
Or
public int[] CheckIDVisitors(string []visitors)
{
using (var sqlConn=new SqlConnection(Properties.Default.MyDbConnectionString))
{
string sql = "SELECT ID FROM Visitors WHERE name IN #names"
var results = conn.Query<int?>(sql, new { names = visitors);
return results.ToArray();
}
}

Combining 2 queries into 1 struct

I have 2 different databases and I need to get results of their queries into 1 structure:
struct my_order
{
public int Id_N
public int OrderId;
public string Discount;
public string CustomerId;
public string ShipAddress;
}
My queries:
my_order ret = new my_order();
SqlConnection con = open_sql(firstDB);
string firstQuery= #" SELECT OrderId, Discount FROM Orders
WHERE Id = " + id_n.ToString(); //some ID that I got earlier
SqlCommand command = new SqlCommand(firstQuery, con);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
ret.Id_N = id_n;
ret.OrderId = reader["OrderId"].ToString();
ret.Discount = reader["Discount"].ToString();
}
SqlConnection second_con = open_sql(secondDB);
string secondQuery= #" SELECT CustomerId, ShipAdress FROM Details
WHERE Id = " + id_n.ToString();
//id_n - some ID that I got earlier(the same as in the first query)
SqlCommand secondCommand = new SqlCommand(secondQuery, second_con);
SqlDataReader secondReader = secondCommand.ExecuteReader();
while (secondReader.Read())
{
ret.Id_N = id_n;
ret.CustomerId = secondReader["CustomerId"].ToString();
ret.ShipAddress = secondReader["ShipAddress"].ToString();
}
The problem is that compiler get the result from the firstQuery and doesn't from the secondQuery. It doesn't get into while(secondReader.Read()).
How to correct it?
PS: I change the code and queries a little bit, because my is too big, but the question is the same.
Regards,
Alexander.
Shooting into the blue here without an error or a more detailed description of what is going wrong, but you have a typo in your code:
string secondQuery= #" SELECT CustomerId, ShipAdress FROM Details WHERE Id = " + id_n.ToString();
ret.ShipAddress= secondReader["ShipAddress"].ToString();
You are writing ShipAdress with one and 2 d's.
Choose the correct version
The reason why it doesn't go into the while (secondReader.Read()) { ... } is because the second query does not return any rows, because there are no rows in table Details with the specified Id value.
you may use the propriete HasRows and IsDBNull(columnIndex) method to check if it contains data
if (secondReader.HasRows)
// Get data
else
//there is no data

Filling custom C# objects from data received stored procedure

public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Address
{
public string City { get; set; }
public string Country { get; set; }
}
/*
* There are 2 c# objects i have shown
* There is a stored procedure in my application which
* returns data for both objects simultaneously
* eg
* select FirstName, LasteName from Users where something="xyz"
* select City,Country from Locations where something="xyz"
*
* both queries are run by single procedure
* Now how can i fill both objects with from that stored procedure in asp.net using c#
*/
Use ADO.NET, open a SqlDataReader on a SqlCommand object executing the SP with the parameters. Use the SqlDataReader.NextResult method to get the second result set.
Basically:
SqlConnection cn = new SqlConnection("<ConnectionString>");
cn.Open();
SqlCommand Cmd = new SqlCommand("<StoredProcedureName>", cn);
Cmd.CommandType = System.Data.CommandType.StoredProcedure;
SqlDataReader dr = Cmd.ExecuteReader(CommandBehavior.CloseConnection);
while ( dr.Read() ) {
// populate your first object
}
dr.NextResult();
while ( dr.Read() ) {
// populate your second object
}
dr.Close();
You could use ADO.net and design a dataset which will create the classes for you so your queries will execute and read into classes which store the data you got.
http://msdn.microsoft.com/en-us/library/aa581776.aspx
That is an excellent tutorial on how to create a data access layer, which is what it sounds like you want to do.
using(SqlConnection connexion = new Sqlconnection(youconenctionstring))
using(SqlCommand command = conenxion.Createcommand())
{
command.Commandtext = "yourProcName";
command.CommandType = CommandType.StoredProcedure;
command.Paramters.Add("#yourparam",yourparamvalue);
connexion.Open();
SqlDataReader reader = command.ExecuteReader();
List<User> users = new List<User>;
List<Adress> adresses = new List<User>;
while(read.Read())
{
User user = new User();
user.firstName = (string) read["FirstName"];
users.Add(user);
}
read.NextResult();
while(read.Read)
{
Address address = new Address();
address.City = (string) read["Name"];
adresses.Add(address);
}
//make what you want with your both list
}
Linq to SQL, Entity Framework, or NHibernate would be my suggestions.
Check out the Enterprise library, specifically the Data Access block from microsoft patterns and practices. Even if you don't use it, you can steal, er... borrow code from it to do what you want.
http://www.codeplex.com/entlib

Categories

Resources