I have DataReader that holds the results from a stored procedure caal. The results consist of two fields ...
UserID
UserName
Normally I bind these results to an ASP.NET dropdownlist control ...
ddlUserList.DataSource = rdr // rdr is the DataReader
ddlUserList.DataTextField = "UserName"
ddlUserList.DataValueField = "UserID"
ddlUserList.DataBind()
However I am now trying to accomplish the same thing using jQuery AJAX. What I am stuck on is how to manually convert the dataset held in the DataReader to JSON. How are multiples values separated? Does this look correct?
{{"UserID":1, "UserName":"Bob"}, {"UserID":2, "UserName":"Sally"},{"UserID":3, "UserName":"Fred"}}
I realize there are libraries out there such as JSON.NET to handle the serialization but I am in the learning stage now and want to make sure I understand everything from the bottom up.
Was wondering if you have tried using System.Web.Script.Serialization.JavaScriptSerializer library?
You can look at Rick Stahl's blog on this:
http://www.west-wind.com/weblog/posts/737584.aspx
Or you could also do something like create a method that will pull out data from the datareader and place it in a list of objects. (See code below). These list of object will be serialized using the JavaScriptSerializer library.
Hope this helps!
public class User
{
public int UserId { get; set; }
public string UserName { get; set;}
}
public class DataLayer
{
public string GetUsers(string connString)
{
string result = null;
List<User> users = null;
// get data using SqlReader
using(var conn = new SqlConnection(connString))
{
using(var cmd = new SqlCommand{ Connection = conn, CommandText = "SELECT * FROM Users", CommandType = CommandType.Text })
{
conn.Open();
var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
if(!reader.HasRows)
return null;
//convert data reader to a list of user objects
users = (List<User>)ConvertToList<User>(ref reader);
conn.Close();
}
}
//convert list of objects in list to json objects
var jsonSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
result = jsonSerializer.Serialize(users);
return result;
}
public static IList<T> ConvertToList<T>(ref SqlDataReader reader)
{
IList<T> result = null;
if (reader.IsClosed)
return result;
result = new List<T>();
T item = default(T);
while (reader.Read())
{
//create item instance
item = (T)Activator.CreateInstance<T>();
//get class property members
var propertyItems = item.GetType().GetProperties();
//populate class property members with data from data reader
for (int ctr = 0; ctr < reader.FieldCount; ctr++)
{
if(reader.GetName(ctr) == propertyItems[ctr].Name)
propertyItems[ctr].SetValue(item, UtilsHelper.GetValue<string>(reader[ctr]), null);
}
//add item to list
result.Add(item);
}
reader.Close();
reader.Dispose();
return result;
}
}
Related
I am trying to connect to SQL Server from the Web API and return a result set as JSON. But my code shown here is not working as expected. I am trying to return the entire query response as a JSON:
[HttpGet]
public HttpResponseMessage Getdetails(string ROOM)
{
string commandText = "SELECT * from [TDB].[dbo].[results_vw] where ROOM = #ROOM_Data";
string connStr = ConfigurationManager.ConnectionStrings["TDBConnection"].ConnectionString;
var jsonResult = new StringBuilder();
using (SqlConnection connection = new SqlConnection(connStr))
{
SqlCommand command = new SqlCommand(commandText, connection);
command.Parameters.Add("#ROOM_Data", SqlDbType.VarChar);
command.Parameters["#ROOM_Data"].Value = ROOM;
connection.Open();
var reader = command.ExecuteReader();
if (!reader.HasRows)
{
jsonResult.Append("[]");
}
else
{
while (reader.Read())
{
jsonResult.Append(reader.GetValue(0).ToString());
}
}
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
response.Content = new StringContent(jsonResult.ToString());
connection.Close();
return response;
}
}
This code returns this result:
333838362692368203368203368203362692368203359544362692368203362692368203362692368203368203
Where I am expecting the JSON as
{"data":
[
{"R_ID":"368203","ROOM":"K2"},
{"R_ID":"368203","ROOM":"K2"}
]}
Now I created a model class called DatabaseResult to store the response but I am not sure how I can store the result in to the model class in the controller
public class DatabaseResult
{
public int r_id { get; set; }
public string room { get; set; }
}
The current result is because you are only return the the value from the first column of each row and adding it to the string builder.
Create a new instance of the model and populate it using the values from the reader for each row.
[HttpGet]
public IHttpActionResult Getdetails(string ROOM) {
string commandText = "SELECT * from [TDB].[dbo].[results_vw] where ROOM = #ROOM_Data";
string connStr = ConfigurationManager.ConnectionStrings["TDBConnection"].ConnectionString;
var jsonResult = new StringBuilder();
using (SqlConnection connection = new SqlConnection(connStr)) {
using (SqlCommand command = new SqlCommand(commandText, connection)) {
command.Parameters.Add("#ROOM_Data", SqlDbType.VarChar);
command.Parameters["#ROOM_Data"].Value = ROOM;
connection.Open();
List<DatabaseResult> records = new List<DatabaseResult>();
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
var row = new DatabaseResult {
r_id = (int)reader["r_id"],
room = (string)reader["room"],
//...other properties.
};
records.Add(row);
}
return Ok(records);
}
}
}
}
The above uses the column names as the indexer to get the values from the reader.
public class GetAreaFromCity : System.Web.Services.WebService
{
[WebMethod]
public GetAreaByCityId ClassForGetCIty(int City_Id)
{
string CS =ConfigurationManager.ConnectionStrings["FOODINNConnectionString"].ConnectionString;
using (SqlConnection con = new SqlConnection(CS))
{
SqlCommand cmd = new SqlCommand("spGetCityById",con);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter parameter = new SqlParameter("#ID", City_Id);
//To assiate this parameter object with cmd object
cmd.Parameters.Add(parameter);
GetAreaByCityId GETAreaByCityId =new GetAreaByCityId();
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
//as WeakReference read data wewant ToString retrive Column value & then polute this property City_Id values
while (reader.Read()){
GETAreaByCityId.City_Id = Convert.ToInt32(reader["City_Id"]);
GETAreaByCityId.Area_Id = Convert.ToInt32(reader["Area_Id"]);
}
return GETAreaByCityId;
//ToString return sql
}
}
}
that's my codes for service page
public class GetAreaByCityId
{
public int Ca_Id {get;set; }
public int City_Id { get; set; }
public int Area_Id { get; set; }
}
that's the class for getting the Area by city
Create Proc [dbo].[spGetCityById]
#ID int
as
Begin
Select Area_Id from
CITIES_AREA where City_Id = #ID
End
GO
and above the database procedure which is data can be retrieve
System.IndexOutOfRangeException: City_Id
at System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)
at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at System.Data.SqlClient.SqlDataReader.get_Item(String name)
at WebApplication1.GetAreaFromCity.ClassForGetCIty(Int32 City_Id) in c:\Users\Mudassir\Documents\Visual Studio 2013\Projects\WebApplication1\WebAppli
the above error i dont know whats the problem
Your stored procedure is returning only Area_Id. Your code in the "while loop" while (reader.Read()){ is attempting to read data from two columns:
City_Id
Area_Id
You could add the column City_Id to the result set for your stored procedure query, BUT you already have that value because you are passing it to the stored procedure as a parameter.
Easiest fix is probably to just change this line:
GETAreaByCityId.City_Id = Convert.ToInt32(reader["City_Id"]);
to this:
GETAreaByCityId.City_Id = City_Id;
I am trying to select a single row on a gridview and have that selection take me to a separate edit page with the data populated. I have the idea of using a session variable to hold the row id and then retrieving the data on page load and populating the text boxes. My question is whether or not this is the best method to go about doing this? I would prefer to not use the inline edit option in gridview as I have too many columns that would require scrolling horizontally. Here is my page load method using the session variable:
if (Session["editID"] != null)
{
dbCRUD db = new dbCRUD();
Recipe editRecipe = new Recipe();
var id = Convert.ToInt32(Session["editID"]);
Session.Remove("editID");
editRecipe = db.SelectRecord(id);
addName.Text = editRecipe.Name;
}
Here is the SelectRecord method that is used to retrieve the row:
public Recipe SelectRecord(int id)
{
Recipe returnedResult = new Recipe();
var dbConn = new SqlConnection(connString);
var dbCommand = new SqlCommand("dbo.selectRecipe", dbConn);
dbCommand.CommandType = CommandType.StoredProcedure;
dbCommand.Parameters.Add("#ID", SqlDbType.Int).Value = id;
dbConn.Open();
SqlDataReader reader = dbCommand.ExecuteReader();
while (reader.HasRows)
{
while (reader.Read())
{
returnedResult.Name = reader["Name"].ToString();
}
}
dbConn.Close();
return returnedResult;
}
I'm probably not utilizing the SQLDataReader appropriately, but my result is no data in the reader therefore no returned data when calling the method. Any help is appreciated - thanks in advance!
Few things you should be aware of here:
1.
You should use while (reader.HasRows) in case your stored procedure returns multiple resultsets. In that case you have to iterate through the result sets. See Retrieving Data Using a DataReader. So, if selectRecipe returns multiple resultsets (I am assuming this is not the case), change your code to this:
while (reader.HasRows)
{
while (reader.Read())
{
returnedResult.Name = reader["Name"].ToString();
}
reader.NextResult();
}
2.If selectRecipe returns single result set, change the while loop to if(){}:
if(reader.HasRows)
{
while (reader.Read())
{
returnedResult.Name = reader["Name"].ToString();
}
}
3. I would probably use using to manage the connection better (using Statement) :
public Recipe SelectRecord(int id)
{
Recipe returnedResult = new Recipe();
using (SqlConnection dbConn = new SqlConnection(connString))
{
var dbCommand = new SqlCommand("dbo.selectRecipe", dbConn);
dbCommand.CommandType = CommandType.StoredProcedure;
dbCommand.Parameters.AddWithValue("#ID", id);
dbConn.Open();
SqlDataReader reader = dbCommand.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
returnedResult.Name = reader["Name"].ToString();
}
}
reader.Close();
}
return returnedResult;
}
The method below uses OracleDataReader.GetValues() to stuff SQL results into an array of objects.
From that array, calling methods can convert, e.g. numbers to longs with Convert.ToInt64(row["FOO_COLUMN"]), but cannot reliably get a date. I cannot use TO_CHAR(some_date_format) because the method must work with SELECT * FROM ....
I've tried checking each column to see if it is an OracleDate via the three lines of commented code below. The contents of the if() statement in question may be incorrect, but it doesn't matter because the if() condition is never met.
I've searched but was surprised that either my search skills need some work or that no one has ever asked this question, probably the former.
public IDictionary<int, IDictionary<string, object>>
dbQuery(string sql, Dictionary<string, object> parameters = null, string connectionString = null) {
var dbResults = new Dictionary<int, IDictionary<string, Object>>();
if(connectionString == null) connectionString = this.defaultQueryConnectionString;
using(var con = new OracleConnection(connectionString)) {
using(var cmd = new OracleCommand(sql, con)) {
cmd.BindByName = true;
if(parameters != null) {
OracleParameter[] parameterArray = new OracleParameter[parameters.Count];
int parameterIndex = 0;
foreach(var parameter in parameters) {
parameterArray[parameterIndex] = new OracleParameter(parameter.Key, parameter.Value);
++parameterIndex;
}
cmd.Parameters.AddRange(parameterArray);
}
con.Open();
var reader = cmd.ExecuteReader();
int columnCount = reader.FieldCount;
object[] columns = new object[columnCount];
int rowNum = 0;
while(reader.Read()) {
reader.GetValues(columns);
var colval = new Dictionary<string, object>();
for(int columnIndex = 0; columnIndex < columnCount; ++columnIndex) {
//if(columns[columnIndex] is OracleDate) {
// columns[columnIndex] = Convert.ToDateTime(columns[columnIndex]);
//}
string colName = reader.GetName(columnIndex).ToUpperInvariant();
colval.Add(colName.ToUpperInvariant(), columns[columnIndex]);
}
dbResults.Add(rowNum, colval);
++rowNum;
}
}
}
return dbResults;
}
DateTime.Parse(reader[column].ToString())
The OracleDateTime object is actually an Array of bytes. Another solution (that does not use string conversion) is:
OracleDateTime oDT = (OracleDateTime) reader[column];
DateTime? myDT = null;
if(!oDT.IsNull)
myDT = new DateTime(oDT.Year, oDT.Month, oDT.Day
, oDT.Hour, oDT.Minute, oDT.Second);
I've decided to start another thread based on the responses I got in this thread:
Asp.Net: Returning a Reader from a Class
I was returning a reader, but members have suggested I'd be better off returning a Dataset instead and also try to seperate the data access tier from the presentation tier.
This is what I have so far:
//my class methods
public DataSet GetSuppliers()
{
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("con_spSuppliersList", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#blogid", HttpContext.Current.Request.QueryString["p"]);
return FillDataSet(cmd, "SuppliersList");
}
//my FillDataSet method
private DataSet FillDataSet(SqlCommand cmd, string tableName)
{
SqlConnection conn = new SqlConnection(connectionString);
cmd.Connection = conn;
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
try
{
conn.Open();
adapter.Fill(ds, tableName);
}
finally
{
conn.Close();
}
return ds;
}
// on my ascx page I call the method like so:
protected void Page_Load(object sender, EventArgs e)
{
//instantiate our class
MyClass DB = new MyClass();
// grab the table of data
DataTable dt = DB.GetSuppliers().Tables["SuppliersList"];
//loop through the results
foreach (DataRow row in dt.Rows)
{
this.supplierslist.InnerHtml += Server.HtmlEncode(row["Address"].ToString()) + "<br/>";
this.supplierslist.InnerHtml += "<b>Tel: </b>" + Server.HtmlEncode(row["Telephone"].ToString()) + "<p/>";
}
}
}
Would anyone like to suggest improvements?
Is my loop 'data tier' or 'presentation tier', should the loop be inside the class and I just return a formatted string instaed of a dataset?
Thanks for all the great advice
I also would use a Typed DataSet or create your own class that holds the properties so you are not dealing with strings like row["Address"] you would say object.Address and get compile time checking.
DataSets have a lot of built in functionality that is nice but also caries with it a lot of overhead that might not be needed in something simple. Creating a simple class with properties and passing that out of your data access layer is probably the simplest way to implement what you want.
Something like this on the DAL (Data Access Layer) side:
//Also pass in the blogID dont have the DAL get the value from the UI layer..
//make the UI layer pass it in.
public IList<Supplier> GetSuppliers(string connectionString, int blogID)
{
IList<Supplier> suppliers = new List<Supplier>();
//wrap with the using statements
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("con_spSuppliersList", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#blogid", blogID);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
suppliers.Add(new Supplier
{
Address = reader.GetString(0),
Telephone = reader.GetString(1)
});
}
}
}
return suppliers;
}
}
public class Supplier
{
//I would have Address an object....but you have as string
//public Address Address { get; set; }
public string Address { get; set; }
public string Telephone { get; set; }
}
//Example if you went with Address class...
public class Address
{
//Whatever you want in the address
public string StreetName { get; set; }
public string Country { get; set; }
public string Region { get; set; }
public string City { get; set; }
}
One thing you should get in the habit of doing is calling Dispose() on your SqlConnection. The best pattern to do this is to use the using statement, which will automatically dispose of it for you. It looks like this:
private DataSet FillDataSet(SqlCommand cmd, string tableName)
{
using(SqlConnection conn = new SqlConnection(connectionString))
{
cmd.Connection = conn;
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
try
{
conn.Open();
adapter.Fill(ds, tableName);
}
finally
{
conn.Close();
}
return ds;
}
}
What you have in the foreach loop in Page_Load, is presentation logic (layout), and IMO this should not be in the code-behind of your page, but in the markup.
I'd suggest that instead of using a foreach loop to construct the HTML output, you should use a databound control (such as a asp:Repeater, DataList or GridView). Then you can bind the repeater to your dataset or datatable and have all the markup where it belongs (in the ASCX file). See this page for an example.
As a general note: you can find lots of tutorials on www.asp.net, e.g. about data access: http://www.asp.net/%28S%28pdfrohu0ajmwt445fanvj2r3%29%29/learn/data-access/