I have an asp.net project done in C# (my C# syntax is very rusty) and am using the built in database it creates with the project. I've created a table called aspnet_Tutorials that (for now) stores two columns of user submitted data: TutorialName and TutorialContent. Very simple, it's a learning project.
What I need to do is create a list from the first column of aspnet_Tutorials to use it to create a "directory" of the tutorials on a page. The part I'm having trouble with, mostly syntactically, is connecting to and iterating over the column to get the values into a list. Could anyone provide a straight forward example of this? And possibly explain what's going on in the code.
public class TutorialsDirDAL
{
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString);
SqlCommand cmd = new SqlCommand();
SqlDataAdapter da = new SqlDataAdapter();
DataSet ds = new DataSet();
public List<string> DisplayTutorials() //parameters? String qry?
{
//query the database table, foreach loop over the data, place it into a list?
}
}
I know how to write simple sql queries. But I've seen a couple different set ups for this in my Googling spree. I currently have the following for a query, feel free to pick it apart or offer a better solution than using a query.
cmd.CommandText = "SELECT * FROM ASPNET_TUTORIALS (TutorialTitle)"
+ "VALUES (#tutorialTitle)";
Thank you!
ebad86's answer is acceptable but since you are obviously learning I think introducing OO principals muddy the water with what you are trying to learn at this point.
Here is a basic method:
private void GetData()
{
//The object that will physically connect to the database
using(SqlConnection cnx = new SqlConnection("<your connection string>")
{
//The SQL you want to execute
SqlCommand cmd = new SqlCommand("SELECT * FROM ASPNET_TUTORIALS");
//Open the connection to the database
cnx.Open();
//execute your command
using (IDataReader dataReader = cnx.ExecuteReader(cmd))
{
//Loop through your results
while(dataReader.Read())
{
//do whatever to the data
ListItem item = new ListItem(Convert.ToString(dataReader["TutorialName"]));
lst.Items.Add(item);
}
}
}
}
This is all very straightforward. The part you are most interested in is the while loop though. The loop will go through all of the returned records and you can do whatever you need to do with them. In my example I have assumed that there is a ListBox named 'lst' and I am simply adding ListItems to it that will have the name of whatever 'TutorialName' is. You can make literally do whatever you need to do with the data at this point. To fit your example (returning a List) you would do this:
private List<string> GetData()
{
List<string> lst = new List<string>();
//The object that will physically connect to the database
using(SqlConnection cnx = new SqlConnection("<your connection string>")
{
//The SQL you want to execute
SqlCommand cmd = new SqlCommand("SELECT * FROM ASPNET_TUTORIALS");
//Open the connection to the database
cnx.Open();
//execute your command
using (IDataReader dataReader = cnx.ExecuteReader(cmd))
{
//Loop through your results
while(dataReader.Read())
{
lst.Add(Convert.ToString(dataReader["TutorialName"]));
}
}
}
return lst;
}
Please respond if you have any questions.
Well you can fetch using data reader and map to the object. I can give you some rough code, which could be like this:
using (IDataReader objDataReader = objDB.ExecuteReader(objCMD))
{
while (objDataReader.Read())
{
DataBaseObject obj = new DataBaseObject();
obj = MapObjectToList(objDataReader);
ObjectList.Add(obj);
}
objDataReader.Dispose();
}
// Mapping Function can be called somewhat like this:
private DataBaseObject MapObjectToList(IDataReader objDataReader)
{
DataBaseObject obj = new DataBaseObject();
obj.Prop1Name = base.GetDataValue<string>(objDataReader, "Column1Name");
obj.Prop2Name = base.GetDataValue<string>(objDataReader, "Column2Name");
return obj;
}
But this is just a rough idea how I would do it.
Related
I'm trying to port some old VB6 code to C# and .NET.
There are a number of places where the old code uses a RecordSet to execute a SQL query and then loop through the results. No problem so far, but inside the loop the code makes changes to the current row, updating columns and even deleting the current row altogether.
In .NET, I can easily use a SqlDataReader to loop through SQL query results, but updates are not supported.
So I've been playing with using a SqlDataAdapter to populate a DataSet, and then loop through the rows in a DataSet table. But the DataSet doesn't seem very smart compared to the VB6's old RecordSet. For one thing, I need to provide update queries for each type of edit I have. Another concern is that a DataSet seems to hold everything in memory at once, which might be a problem if there are many results.
What is the best way to duplicate this behavior in .NET? The code below shows what I have so far. Is this the best approach, or is there another option?
using (SqlConnection connection = new SqlConnection(connectionString))
{
DataSet dataset = new DataSet();
using (SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand(query, connection)))
{
adapter.Fill(dataset);
DataTable table = dataset.Tables[0];
foreach (DataRow row in table.Rows)
{
if ((int)row["Id"] == 4)
{
if ((int)row["Value1"] > 0)
row["Value2"] = 12345;
else
row["Value3"] = 12345;
}
else if ((int)row["Id"] == 5)
{
row.Delete();
}
}
// TODO:
adapter.UpdateCommand = new SqlCommand("?", connection);
adapter.DeleteCommand = new SqlCommand("?", connection);
adapter.Update(table);
}
}
Note: I'm new to the company and can't very well tell them they have to change their connection strings or must switch to Entity Framework, which would be my choice. I'm really looking for a code-only solution.
ADO.NET DataTable and DataAdapter provide the closest equivalent of ADO Recordset with applies separation of concens principle. DataTable contains the data and provides the change tracking information (similar to EF internal entity tracking) while DataAdapter provides a standard way to populate it from database (Fill method) and apply changes back to the database (Update method).
With that being said, what are you doing is the intended way to port the ADO Recordset to ADO.NET. The only thing you've missed is that you are not always required to specify Insert, Update and Delete commands. As soon as your query is querying a single table (which I think was a requirement to get updateable Recordset anyway), you can use another ADO.NET player called DbCommandBuilder:
Automatically generates single-table commands used to reconcile changes made to a DataSet with the associated database.
Every database provider provides implementation of this abstract class. The MSDN example for SqlCommandBuilder is almost identical to your sample, so all you need before calling Update is (a bit counterintuitive):
var builder = new SqlCommandBuilder(adapter);
and that's it.
Behind the scenes,
The DbCommandBuilder registers itself as a listener for RowUpdating events that are generated by the DbDataAdapter specified in this property.
and dynamically generates the commands if they are not specifically set in the data adapter by you.
I came up with an (untested) solution for a data table.
It does require you to do some work, but it should generate update and delete commands for each row you change or delete automatically, by hooking up to the RowChanged and RowDeleted events of the DataTable.
Each row will get it's own command, equivalent to ADODB.RecordSet update / delete methods.
However, unlike the ADODB.RecordSet methods, this class will not change the underling database, but only create the SqlCommands to do it. Of course, you can change it to simply execute them on once they are created, but as I said, I didn't test it so I'll leave that up to you if you want to do it. However, please note I'm not sure how the RowChanged event will behave for multiple changes to the same row. Worst case it will be fired for each change in the row.
The class constructor takes three arguments:
The instance of the DataTable class you are working with.
A Dictionary<string, SqlDbType> that provides mapping between column names and SqlDataTypes
An optional string to represent table name. If omitted, the TableName property of the DataTable will be used.
Once you have the mapping dictionary, all you have to do is instantiate the CommandGenerator class and iterate the rows in the data table just like in the question. From that point forward everything is automated.
Once you completed your iteration, all you have to do is get the sql commands from the Commands property, and run them.
public class CommandGenerator
{
private Dictionary<string, SqlDbType> _columnToDbType;
private string _tableName;
private List<SqlCommand> _commands;
public CommandGenerator(DataTable table, Dictionary<string, SqlDbType> columnToDbType, string tableName = null)
{
_commands = new List<SqlCommand>();
_columnToDbType = columnToDbType;
_tableName = (string.IsNullOrEmpty(tableName)) ? tableName : table.TableName;
table.RowDeleted += table_RowDeleted;
table.RowChanged += table_RowChanged;
}
public IEnumerable<SqlCommand> Commands { get { return _commands; } }
private void table_RowChanged(object sender, DataRowChangeEventArgs e)
{
_commands.Add(GenerateDelete(e.Row));
}
private void table_RowDeleted(object sender, DataRowChangeEventArgs e)
{
_commands.Add(GenerateDelete(e.Row));
}
private SqlCommand GenerateUpdate(DataRow row)
{
var table = row.Table;
var cmd = new SqlCommand();
var sb = new StringBuilder();
sb.Append("UPDATE ").Append(_tableName).Append(" SET ");
var valueColumns = table.Columns.OfType<DataColumn>().Where(c => !table.PrimaryKey.Contains(c));
AppendColumns(cmd, sb, valueColumns, row);
sb.Append(" WHERE ");
AppendColumns(cmd, sb, table.PrimaryKey, row);
cmd.CommandText = sb.ToString();
return cmd;
}
private SqlCommand GenerateDelete(DataRow row)
{
var table = row.Table;
var cmd = new SqlCommand();
var sb = new StringBuilder();
sb.Append("DELETE FROM ").Append(_tableName).Append(" WHERE ");
AppendColumns(cmd, sb, table.PrimaryKey, row);
cmd.CommandText = sb.ToString();
return cmd;
}
private void AppendColumns(SqlCommand cmd, StringBuilder sb, IEnumerable<DataColumn> columns, DataRow row)
{
foreach (var column in columns)
{
sb.Append(column.ColumnName).Append(" = #").AppendLine(column.ColumnName);
cmd.Parameters.Add("#" + column.ColumnName, _columnToDbType[column.ColumnName]).Value = row[column];
}
}
}
As I wrote, this is completely untested, but I think it should be enough to at least show the general idea.
Your constraints:
Not using Entity Framework
DataSet seems to hold everything in memory at once, which might be a
problem if there are many results.
a code-only solution ( no external libraries)
Plus
The maximum number of rows that a DataTable can store is 16,777,216
row MSDN
To get high performance
//the main class to update/delete sql batches without using DataSet/DataTable.
public class SqlBatchUpdate
{
string ConnectionString { get; set; }
public SqlBatchUpdate(string connstring)
{
ConnectionString = connstring;
}
public int RunSql(string sql)
{
using (SqlConnection con = new SqlConnection(ConnectionString))
using (SqlCommand cmd = new SqlCommand(sql, con))
{
cmd.CommandType = CommandType.Text;
con.Open();
int rowsAffected = cmd.ExecuteNonQuery();
return rowsAffected;
}
}
}
//------------------------
// using the class to run a predefined patches
public class SqlBatchUpdateDemo
{
private string connstring = "myconnstring";
//run batches in sequence
public void RunBatchesInSequence()
{
var sqlBatchUpdate = new SqlBatchUpdate(connstring);
//batch1
var sql1 = #"update mytable set value2 =1234 where id =4 and Value1>0;";
var nrows = sqlBatchUpdate.RunSql(sql1);
Console.WriteLine("batch1: {0}", nrows);
//batch2
var sql2 = #"update mytable set value3 =1234 where id =4 and Value1 =0";
nrows = sqlBatchUpdate.RunSql(sql2);
Console.WriteLine("batch2: {0}", nrows);
//batch3
var sql3 = #"delete from mytable where id =5;";
nrows = sqlBatchUpdate.RunSql(sql3);
Console.WriteLine("batch3: {0}", nrows);
}
// Alternative: you can run all batches as one
public void RunAllBatches()
{
var sqlBatchUpdate = new SqlBatchUpdate(connstring );
StringBuilder sb = new StringBuilder();
var sql1 = #"update mytable set value2 =1234 where id =4 and Value1>0;";
sb.AppendLine(sql1);
//batch2
var sql2 = #"update mytable set value3 =1234 where id =4 and Value1 =0";
sb.AppendLine(sql2);
//batch3
var sql3 = #"delete from mytable where id =5;";
sb.AppendLine(sql3);
//run all batches
var nrows = c.RunSql(sb.ToString());
Console.WriteLine("all patches: {0}", nrows);
}
}
I simulated that solution and it's working fine with a high performance because all updates /delete run as batch.
I'm using a MySQL local database, connecting to the database is not a problem (anymore). I have a small-scale database with around 6 different tables, each with around 4-6 columns, and rows <100 (not working with large data).
I am creating a WPF application that only ever needs to SELECT data from these databases, it never needs to add to them. The database is filled with static data which I will need to run SELECT statements on it and then use the results to display in my WPF app.
I need to make a function in my DBHandler class which can then be called from any other class in my system, to query the database with a specified SELECT statement, and then use the results. The problem is that my queries will vary - sometimes I might be calling for one column, such as;
(SELECT id FROM students WHERE name = 'Conor')
Sometimes I might be calling for multiple rows in a more complex statement.. such as this (pseudo):
(SELECT name, address FROM destinations WHERE long, lat intersects_with (SELECT long, lat FROM trains))
Whenever I call this function with a query, I will always be expecting the format of the data response, so if I just return a List<> or array, it should be no problem accessing the data even though the function is generic and not specific for one query or table.
So far I have tried this:
public static MySqlDataReader Query(string SQLQuery)
{
using (MySqlConnection con = new MySqlConnection(connectionString))
{
con.Open();
MySqlCommand command = new MySqlCommand(SQLQuery, con);
MySqlDataReader reader = command.ExecuteReader();
return reader;
}
}
// Some other class
MySqlDataReader reader = DBHandler.Query("SELECT * FROM destinations");
while (reader.Read())
{
MessageBox.Show(reader[0].ToString());
}
This doesn't work, because it complains the reader is closed. I presume I can't simply return a MySqlDataReader object.
My next thought process would be to do the actual query and return all the data in this Query function, and store all the results which can then be returned. But how I return the data is my main issue, because it needs to be generic for variable SELECT queries, so it can't have a fixed size for number of rows or columns returned. I thought maybe I could store it in a List<>, or a List<> within a List<>, but I'm really not sure on how to lay it out.
I know this is asking a lot but it is boggling my mind - I don't know how to make this generic SELECT function, but I know it will be really helpful as I will just need to call this whenever I need to get data in another part of the system.
Thank you!
You cannot try to use a DataReader when its connection has been closed. So, when your code exits the using block, the connection is closed as well the reader. However, you can pass to your Query method an Action delegate that receives a MySqlDataReader. This function will be defined by the caller of Query so you can customize it for your different tables while keeping a generic approach to the boilerplate code used to open, query and read the database.
public static MySqlDataReader Query(string SQLQuery, Action<MySqlDataReader> loader)
{
using (MySqlConnection con = new MySqlConnection(connectionString))
{
con.Open();
using(MySqlCommand command = new MySqlCommand(SQLQuery, con))
using(MySqlDataReader reader = command.ExecuteReader())
{
// here you can pass the reader, you are still inside the using block
while(reader.Read())
loader.Invoke(reader)
}
}
}
In the caller code you could write
List<Destination> destinations = new List<Destination>();
MySqlDataReader reader = DBHandler.Query("SELECT * FROM destinations", dataLoaderForDestination);
Console.WriteLine("Loaded " + destinations.Count + " destinations");
private void dataLoaderForDestination(MySqlDataReader reader)
{
Destination dest = new Destination();
dest.Address = reader.GetString(0);
dest.Nation = reader.GetInt32(1);
...
destinations.Add(dest);
}
Of course in a different point of your code you could pass the reference to a different Action delegate tailored for a different set of data returned by your query
List<Student> students = new List<Student>();
private void dataLoaderForStudents(MySqlDataReader reader)
{
Student st = new Student();
st.Name = reader.GetString(0);
st.Class = reader.GetInt32(1);
students.Add(st);
}
a reader is online, you need to loop inside (using connection), because if you leave the using, the connction is disposed and closed
Hi I am trying to create CRUD functions in C# but am stuck on my first one which is FetchALL, as so far it says not all code path returns a value.
Heres my code so far
public SqlDataReader FetchAll(string tableName)
{
using (SqlConnection conn = new SqlConnection(_ConnectionString,))
{
string query = "SELECT * FROM " + tableName;
SqlCommand command = new SqlCommand(query, conn);
using (SqlDataReader reader = command.ExecuteReader())
conn.Open();
conn.Close();
}
}
}
}
I can give you more information, thanks
You have a return type of SqlDataReader, but you aren't returning anything anywhere in your code. At the very least you should declare your data reader and return it like this:
public SqlDataReader FetchAll(string tableName)
{
SqlDataReader reader;
using (SqlConnection conn = new SqlConnection(_ConnectionString))
{
string query = "SELECT * FROM " + tableName;
// added using block for your command (thanks for pointing that out Alex K.)
using (SqlCommand command = new SqlCommand(query, conn))
{
conn.Open(); // <-- moved this ABOVE the execute line.
reader = command.ExecuteReader(); // <-- using the reader declared above.
//conn.Close(); <-- not needed. using block handles this for you.
}
}
return reader;
}
Note, I've noted a few other problems I saw as well, which you can see by my comments.
Also, I want to point out something very important: you should always avoid string concatenation in queries as this opens you up to the risk of a SQL injection attack (as gmiley has duly pointed out). In this case, you should create an enum which contains values associated with all the possible table names, and then use a dictionary to look up the table names based on their enum values. If a user provides an invalid/unknown value, you would then thrown an argument exception.
This isn't the end of your problems, though (as Default has pointed out). You can't create the connection in a using block, which disposes and closes as soon as it exits the block, and then use the SqlDataReader that is returned from the method. If I were you, I'd return a DataSet instead of a SqlDataReader. Here's how I'd do it:
First, create your enum of possible table values:
public enum Table
{
FirstTable,
SecondTable
}
And a dictionary that maps table enum values to the table names (which you will populate in your static constructor):
private static Dictionary<Table, string> _tableNames = new Dictionary<Table, string>(); // populate this in your static constructor.
And then here is your method to fetch the data:
public static System.Data.DataSet FetchAll(Table fromTable)
{
var ret = new System.Data.DataSet();
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
{
string tableName = "";
if (!_tableNames.TryGetValue(fromTable, out tableName)) throw new ArgumentException(string.Format(#"The table value ""{0}"" is not known.", fromTable.ToString()));
string query = string.Format("SELECT * FROM {0}", tableName);
using (var command = new System.Data.SqlClient.SqlCommand(query, conn))
{
using (var adapter = new System.Data.SqlClient.SqlDataAdapter(command))
{
adapter.Fill(ret);
}
}
}
return ret;
}
One final note, I'd advise you name your class-level variables with lower camel case per convention, e.g. _connectionString.
Firstly you aren't returning anything from the method. I'd add, are you sure you want to return a SqlDataReader? It is declared within a using block, so it will be closed by the time you return it anyway. I think you should re-evaluate what this function should return.
You need a return statment for the method to return a value.
I have a functioning stored procedure which returns the correct data when executed manually. There are several rows of data in the output. However, the following code I have is always resulting in no rows of data being added to the DataTable.
var commandString = string.Format(#"EXEC MyStoredProcedure {0}", SomeParameter);
var dataTable = new DataTable();
using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();
using (var adapter = new SqlDataAdapter(commandString, ConnectionString))
{
using (new SqlCommandBuilder(adapter))
{
adapter.Fill(dataTable);
adapter.Update(dataTable);
}
}
}
var result = (from DataRow row in dataTable.Rows
select new MyModelClass
{
SomeString = (string) row["SomeString"],
SomeValue = (string) row["SomeValue"],
}).ToList();
Debug.WriteLine("Results: " + result.Count);
I am not sure why the code is resulting in no rows of data. Where am I going wrong? I suspect it is because I have an incorrect understanding of how DataTable works. How should I fix the code?
Basically, your code should look something like this:
string ConnectionString = "<Your connection string here>";
string procedureName = "<your stored procedure name here>";
string ParamName = "#<Parameter name>"; // NOTE: the '#' is INSIDE the string!
DataSet ds = new DataSet();
using (var connection = new SqlConnection(ConnectionString))
{
var cmd = new SqlCommand(procedureName, connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(ParamName, SqlDbType.Int).Value = 5;
using (var adapter = new SqlDataAdapter(cmd))
{
adapter.Fill(ds);
}
}
Sorry to be late to the party but I just want to confirm something.
I noticed that in the OP the asker uses the adapter to fill a DataTable
In the accepted answer, however, the adapter is used to fill a DataSet
Well, that's great - I also discovered this myself. The DataTable will contain 0 rows ase well as 0 columns, but use it to fill a DataSet instead, and it works fine.
So the real question here is: Why does it work with a DataSet when it doesn't with a DataTable?
I have a suspicion and I'm really here to try and prove it right or wrong. In my case I found that the SP started returning this weird empty DataTable as soon as my SP contained anything like a DELETE or an INSERT. In my case, where the original SP was just a simple SELECT from some data, I had to add some complexity which meant I first had to populate a Table Variable using INSERTs, and then SELECT from that. And that's when I ran into this problem.
So, #user9993, if you're still around can you just tell me.... does your SP do more than just SELECT? Does it do updates or deletes even if it's just to something like a Temporary Table or a Table Variable?
I want to store many record that I query from database in list , one record has 10 fields, in order to loop it later.
But I have no idea to do that. Anyone can answer me please.
Below is a good practice to store data and loop through among them.
Create Model/POCO class as:
public class DataClassName
{
public int Id { get; set; }
public string Name { get; set; }
//Create properties according to your fields
}
Fill and get data list:
public List<DataClassName> GetDataList()
{
List<DataClassName> dataList = new List<DataClassName>();
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "select * from TargetTableName";
cmd.CommandType = CommandType.Text;
try
{
using (SqlConnection connection =
new SqlConnection("YourConnectionString"))
{
cmd.Connection = connection;
connection.Open();
SqlDataReader reader = cmd.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
dataList.Add(
new DataClassName()
{
Id = Convert.ToInt32(reader["ID"]),
Name = Convert.ToString(reader["Name"])
//Set all property according to your fields
});
}
}
}
}
catch(Exception ex_)
{
//Handle exception
}
return dataList;
}
Save data that is returned from GetDataList() into your datalist and loop through among the data as required.
Here's how you'd go about storing it in a DataTable:
SqlConnection conn = new SqlConnection("yourConnectionStringHere");
SqlCommand GetData = new SqlCommand();
GetData.Connection = conn;
GetData.CommandText = "select * from yourTable"; // or whatever your query is, whether ad hoc or stored proc
// add parameters here if your query needs it
SqlDataAdapter sda = new SqlDataAdapter(GetData);
DataTable YourData = new DataTable();
try
{
sda.Fill(YourData);
}
catch
{
sda.Dispose();
conn.Dispose();
}
If you have 10 fields, you'd be hard-pressed to store your data in a List<T> object. Your best bet would be to create a class tailored to the data you are looking to retrieve (if you want to take it a step further than the DataTable implementation above) with corresponding properties/fields.
Perhaps you could give a bit more information...
If you use the entity framework or similar to query the database, it will probably return an enumerable object.. you just need to call .ToList() on this to save it as a list.
Do you mean that you want to store this across web requests? Then you could store it in the HttpRuntime.Cache collection, allowing for it to expire after some time.
Alternatively store it in a static property. Session is also an option but it doesn't sound like the best option for this