How to convert a data reader to dynamic query results - c#

I have a View that typically gets query results from a WebMatrix Query (IEnumerable<dynamic> data type), and displays the results in a table:
#model MySite.Models.Entity
#foreach(var row in Model.Data)
{
<tr>
#foreach (var column in row.Columns)
{
<td>#column<span>:</span> #row[column]</td>
}
</tr>
}
Here's my model where I query the database:
public class Entity
{
public dynamic Data {get; set; }
public Entity(String table)
{
if (table == "User" || table == "Group)
{
WebMatrix.Data.Database db = new WebMatrix.Data.Database();
db.Open(ConString);
Data = db.Query("SELECT * FROM " + table);
}
else
{
using (OdbcConnection con = ne4w OdbcConnection(ConString))
{
OdbcCommand com = new OdbcCommand("Select * From " + table);
command.CommandType = System.Data.CommandType.Text;
connection.Open();
OdbcDataReader reader = command.ExecuteReader();
Here's all the different things I've tried from reading various other posts:
// Atempt 1
Data = reader;
// Error in view, 'Invalid attempt to call FieldCount when reader is closed' (on 'var row `in` Model.Data')
// Atempt 2
Data = reader.Cast<dynamic>;
// Error: 'Cannot convert method group "Cast" to non-delegate type "dynamic". Did you intend to invoke the method?
// Atempt 3
Data = reader.Cast<IEnumerable<dynamic>>;
// Error same as Atempt 2
// Atempt 4
Data = reader.Cast<IEnumerable<string>>;
// Error same as Atempt 2
}
}
}
}
I'm looking for the best way to get the reader object to a IEnumerable<dynamic> object.
Please note this is a simplified example, and while the reason for the two query types is not obvious, they are necessary in my code.

You're missing basic C# syntax.
Data = reader;
// You cant do this. You have to loop the reader to get the values from it.
// If you simply assign reader object itself as the data you wont be
// able to get data once the reader or connection is closed.
// The reader is typically closed in the method.
Data = reader.Cast<dynamic>;
// You should call the Cast method. And preferably execute the resulting query.
// As of now you're merely assigning method reference to a variable
// which is not what you want.
// Also bear in mind that, as I said before there's no real benefit in casting to dynamic
Data = reader.Cast<IEnumerable<dynamic>>;
// Cast method itself returns an IEnumerable.
// You dont have to cast individual rows to IEnumerable
Data = reader.Cast<IEnumerable<string>>;
// Meaningless I believe.
// The data you get from database is not always strings
The major mistake you make is not calling the method. This is what you want:
Data = reader.Cast<IDataRecord>().ToList();
^^ // notice the opening and closing parentheses
You could go about this a number of ways depending on what is easier to process (say, to display in front-end).
Return data records.
public IEnumerable<IDataRecord> SelectDataRecord()
{
....
using (var reader = cmd.ExecuteReader())
foreach (IDataRecord record in reader as IEnumerable)
yield return record; //yield return to keep the reader open
}
Return ExpandoObjects. Perhaps this is what you wanted?
public IEnumerable<dynamic> SelectDynamic()
{
....
using (var reader = cmd.ExecuteReader())
{
var names = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToList();
foreach (IDataRecord record in reader as IEnumerable)
{
var expando = new ExpandoObject() as IDictionary<string, object>;
foreach (var name in names)
expando[name] = record[name];
yield return expando;
}
}
}
Return sequence of property bag
public IEnumerable<Dictionary<string, object>> SelectDictionary()
{
....
using (var reader = cmd.ExecuteReader())
{
var names = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName).ToList();
foreach (IDataRecord record in reader as IEnumerable)
yield return names.ToDictionary(n => n, n => record[n]);
}
}
Return sequence of plain object array
public IEnumerable<List<object>> SelectObjectArray()
{
....
using (var reader = cmd.ExecuteReader())
{
var indices = Enumerable.Range(0, reader.FieldCount).ToList();
foreach (IDataRecord record in reader as IEnumerable)
yield return indices.Select(i => record[i]).ToList();
}
}
Return data rows
public IEnumerable<DataRow> SelectDataRow()
{
....
using (var reader = cmd.ExecuteReader())
{
var table = new DataTable();
table.BeginLoadData();
table.Load(reader);
table.EndLoadData();
return table.AsEnumerable(); // in assembly: System.Data.DataSetExtensions
}
}
Last but not least, if it helps, you can return a strongly-typed sequence without any manual plumbing. You can use expression trees to compile code at run-time. See this for e.g.

Try looping the reader results:
OdbcDataReader reader = command.ExecuteReader();
while(reader.Read())
{
var item = reader["yourField"].ToString();
}
It looks like you are just trying to set an object to the unexecuted IQueriable result of command.ExecuteReader();

Related

How to execute a stored procedure returning a pivot table in Web API

How to execute a stored procedure returning a pivot table and return data as JSON in a API. I want to display its data on a web page using API. What I'm getting is just a list of objects but not the data in it.
My procedure is returning 983 records of table rows in it... But column change according to passed parameters
So for one kind of parameter i can have 20 column buy for the other i can have just 1
By executing procedure using
var ls = db.Database.SqlQuery<dynamic>("exec [Sp_StoreItemParty] 'Amount','01-Apr-2020', '06-Sep-2020','', '', '', ''").ToList();
return Json(ls, JsonRequestBehavior.AllowGet);
How to execute a stored procedure returning a pivot table and return it as Web API in JSON format . All i get is a list of objects but none of the objects can further be accessed or further displayed in Postman on On Firefox.
SqlQuery works only with known types, not dynamic. You can
Either create a class that represents your result record and use that. Or
Use a data reader as shown here and copy pasted below.
public static IEnumerable<dynamic> CollectionFromSql(this DbContext dbContext, string Sql, Dictionary<string, object> Parameters)
{
using (var cmd = dbContext.Database.GetDbConnection().CreateCommand())
{
cmd.CommandText = Sql;
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
foreach (KeyValuePair<string, object> param in Parameters)
{
DbParameter dbParameter = cmd.CreateParameter();
dbParameter.ParameterName = param.Key;
dbParameter.Value = param.Value;
cmd.Parameters.Add(dbParameter);
}
//var retObject = new List<dynamic>();
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var dataRow = GetDataRow(dataReader);
yield return dataRow ;
}
}
}
}
private static dynamic GetDataRow(DbDataReader dataReader)
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var fieldCount = 0; fieldCount < dataReader.FieldCount; fieldCount++)
dataRow.Add(dataReader.GetName(fieldCount), dataReader[fieldCount]);
return dataRow;
}
Usage:
List<dynamic> MyList = MyDbContext.CollectionFromSql("SELECT * FROM \"User\" WHERE UserID = #UserID",
new Dictionary<string, object> { { "#UserID", 1 } }).ToList();
because your object is dynamic object you have to resolve the structure of the object in runtime by reflection.
Then when you have the name and the value of each property of the object you can manually build your json.
Type myType = myObject.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(myType.GetProperties());
foreach (PropertyInfo prop in props)
{
String propName = prop.name;
object propValue = prop.GetValue(myObject, null);
// Do something with propValue
}
small tip:
Reflection can be very efficient and damned complex logic.
if you can change the stored procedure to return fixed subset of column it will faster and simpler.

Dump rows from Oracle.ManagedDataAccess.Client.OracleDataReader to a collection that survives closed connection

I'm trying to write a simple helper function that connects to Oracle database using Oracle.ManagedDataAccess.Core library, and returns the rows.
Returned object should survive when connection to database is closed. Something similar to the following. Not sure what the will be.
public <sometype> GetOracleResults(string connectionString, string cmdText, List<OracleParameter> oracleParameters)
{
<sometype> results = null;
try
{
using (OracleConnection oracleConnection = new OracleConnection(connectionString))
{
oracleConnection.Open();
using (OracleCommand oracleCommand = new OracleCommand(cmdText, oracleConnection))
{
foreach (var param in oracleParameters)
{
oracleCommand.Parameters.Add(param);
}
OracleDataReader oracleDataReader = oracleCommand.ExecuteReader();
if(oracleDataReader.HasRows)
{
results = new <sometype>();
while (oracleDataReader.Read())
{
//loop through the reader and add results
return results;
}
}
}
}
}
catch (Exception)
{
//todo
throw;
}
}
Does it have to be strongly typed? If not, a coworker showed me sticking the data in a List of Dictionary items, E.G.:
var columns = new List<string>();
for (int i = 0; i < myReader.FieldCount; i++)
{
columns.Add(myReader.GetName(i).Trim());
}
while (myReader.Read() && results.Count < 200)
{
Dictionary<string, string> row = new Dictionary<string, string>();
foreach (var column in columns)
{
row.Add(column, Convert.ToString(myReader[column], CultureInfo.InvariantCulture).Trim());
}
results.Add(row);
}
I suppose if you know the schema so that the generic type coming into the method had property names matching the column names, then you could use reflection to match up the column results with the C# object.

Problems with linq in getting data from list

I have problem
I need to get list from list in linq. But it type of list is unknown on compilation stage.
using(var context = MyDbContext())
{
var list = (from p in context.Employee select p).ToList()
}
I dont know what just property (change Employee)
I want to do something that
public IList<T> GetAll(string propName)
{
using (var context = new ModelContext())
{
return (from p in context.GetType().GetProperty(propName) select p).ToList();
}
}
The DbContext.Database property provides an API that allows you to perform ADO.NET operations directly. The GetDbConnection method returns a DbConnection object representing the context's underlying connection. From that point, you can revert to the familiar ADO.NET APIs:
public dynamic GetAll(string propName)
{
using (var context = new SampleContext())
using (var command = context.Database.GetDbConnection().CreateCommand())
{
//Escape propName to prevent SQL injection
var builder = new SqlCommandBuilder();
string escapedTableName = builder.QuoteIdentifier(propName);
command.CommandText = $"SELECT * From {escapedTableName}";
context.Database.OpenConnection();
using (var dataReader= command.ExecuteReader())
{
var dataTable = new DataTable();
dataTable.Load(dataReader);
return dataTable ;
}
}
}

QueryMultiple Result Set Order Changed

I am executing a stored procedure using QueryMultiple to return multiple sets of data.
var gridReader = db.QueryMultiple("sp",
parameters,
commandType: CommandType.StoredProcedure);
I can very easily get each set given I know the order they will come back in.
SELECT * FROM dbo.Set1;
SELECT * FROM dbo.Set2;
SELECT * FROM dbo.Set3;
var set1 = gridReader.Read<Set1>();
var set2 = gridReader.Read<Set2>();
var set3 = gridReader.Read<Set3>();
However, I am in a situation where the order they will come back in may change. Another developer could decide to change the order for whatever reason. The stored procedure now becomes this:
SELECT * FROM dbo.Set1;
SELECT * FROM dbo.Set3;
SELECT * FROM dbo.Set2;
How can I handle this?
My initial attempt was to iterate each grid, checking the column names. This seemed to work well at first, but I wasn't able to figure out how to then project the grid into a class, besides manually setting each field. The main reason I'm using Dapper is so it can do this for me.
while (true)
{
var grid = gridReader.Read();
IDictionary<string, object> row = grid.FirstOrDefault();
if (row == null)
break;
if (row.Keys.Contains("Set1_UniqueColumnName"))
{
// Need something like grid.Read<Set1>();
}
else if (row.Keys.Contains("Set2_UniqueColumnName")) { }
else if (row.Keys.Contains("Set3_UniqueColumnName")) { }
}
My second idea was to read each grid into a class, check the unique fields of the class for nulls/default values, and trying the next class if the test failed. This obviously won't work though. .Read() will return the next grid of results. This solution would require me to be able to read the same grid over and over.
Dapper provides an IDataReader.GetRowParser extension method that enables type switching per row. From the Dapper docs here...
Usually you'll want to treat all rows from a given table as the same
data type. However, there are some circumstances where it's useful to
be able to parse different rows as different data types. This is where
IDataReader.GetRowParser comes in handy.
Imagine you have a database table named "Shapes" with the columns: Id,
Type, and Data, and you want to parse its rows into Circle, Square, or
Triangle objects based on the value of the Type column.
var shapes = new List<IShape>();
using (var reader = connection.ExecuteReader("select * from Shapes"))
{
// Generate a row parser for each type you expect.
// The generic type <IShape> is what the parser will return.
// The argument (typeof(*)) is the concrete type to parse.
var circleParser = reader.GetRowParser<IShape>(typeof(Circle));
var squareParser = reader.GetRowParser<IShape>(typeof(Square));
var triangleParser = reader.GetRowParser<IShape>(typeof(Triangle));
var typeColumnIndex = reader.GetOrdinal("Type");
while (reader.Read())
{
IShape shape;
var type = (ShapeType)reader.GetInt32(typeColumnIndex);
switch (type)
{
case ShapeType.Circle:
shape = circleParser(reader);
break;
case ShapeType.Square:
shape = squareParser(reader);
break;
case ShapeType.Triangle:
shape = triangleParser(reader);
break;
default:
throw new NotImplementedException();
}
shapes.Add(shape);
}
}
You'll need to get access to the IDataReader that the GridReader wraps or change your code to use the good old-fashioned ADO.NET SqlConnection & SqlCommand objects like this...
using (command = new SqlCommand("sp", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(parameters);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
// read row columns
}
}
}
Davmos's answer pointed me in the right direction. Needed to use a combination of ADO.NET and Dapper. Essentially use ADO.NET to retrieve and iterate through the data, but use Dapper to parse the rows into my objects. Note the use of FieldCount in the while loop in case a result set actually does return 0 rows. We want it to move on to the next result set, not break out of the loop.
Set1 set1 = null;
var set2 = new List<Set2>();
Set3 set3 = null;
using (var command = new SqlCommand("sp", conn))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(parameters);
command.Connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.FieldCount > 0)
{
var set1Parser = reader.GetRowParser<Set1>();
var set2Parser = reader.GetRowParser<Set2>();
var set3Parser = reader.GetRowParser<Set3>();
var isSet1 = HasColumn(reader, "Set1_UniqueColumnName");
var isSet2 = HasColumn(reader, "Set2_UniqueColumnName");
var isSet3 = HasColumn(reader, "Set3_UniqueColumnName");
while (reader.Read())
{
if (isSet1)
{
set1 = set1Parser(reader);
}
else if (isSet2)
{
set2.Add(set2Parser(reader));
}
else if (isSet3)
{
set3 = set3Parser(reader);
}
}
reader.NextResult();
}
}
}
public static bool HasColumn(IDataReader reader, string columnName)
{
for (var i = 0; i < reader.FieldCount; i++)
{
if (reader.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
return false;
}

xml attributes from SQL in C#

C# winform: I asked a similar Q but didn't reached to the solution so i want to make it more clear, I have a string let suppose
str = "<sample>
<sample1 name="val1">
<sample2 name="val2">
</sample2>
<sample2 name="val3">
<groupbox name="val4">
<field type="textArea" x="xxx" />
</groupbox>
</sample2>
</sample1>
<sample1 name="abc">
</sample1>
<sample1 name="xyz">
</sample1>
</sample>"
i want to get the attributes and thier values from this string and place it in gridView notice that this string is just an example it could be changed. or display in any control like richTextField .... etc
I've given this solution before - this code will parse your one XML string and it will return the list of attributes and their values - so what else / what more do you need??
private static List<KeyValuePair<string, string>> ParseForAttributeNames(string xmlContent)
{
List<KeyValuePair<string, string>> attributeNamesAndValues = new List<KeyValuePair<string, string>>();
XDocument xmlDoc = XDocument.Parse(xmlContent);
var nodeAttrs = xmlDoc.Descendants().Select(x => x.Attributes());
foreach (var attrs in nodeAttrs)
{
foreach (var attr in attrs)
{
string attributeName = attr.Name.LocalName;
string attributeValue = attr.Value;
attributeNamesAndValues.Add(new KeyValuePair<string, string>(attributeName, attributeValue));
}
}
return attributeNamesAndValues;
}
If you can explain in good, comprehensible English what else you need, I might make the effort to answer you once again with even more info.... but you need to be clear and precise about what it is you need - otherwise me as an illiterate idiot won't be able to answer......
try this:
StringReader rdr = new StringReader(str);
DataSet ds = new DataSet();
ds.ReadXml(str);
DataTable dt = ds.Tables[0];
datagridview.datasource = dt;
For your other question that keeps coming up over and over again (grabbing XML attributes from SQL Server) - this is a complete sample that will
read all rows from a table and extract all the XML columns
parse all the XML contents into a list of attribute name/values
return a bindable list of attribute name/value pairs, which you can bind to a ListView, a GridView - whatever you like.
Please check it out - and if it doesn't meet your needs, please explain in comprehensible English what it is that's still missing.....
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Xml.Linq;
namespace GrabAndParseXml
{
internal class AttrNameAndValue
{
public string AttrName { get; set; }
public string AttrValue { get; set; }
}
class Program
{
static void Main(string[] args)
{
// grab *ALL* the "XmlContent" columns from your database table
List<string> xmlContent = GrabXmlStringsFromDatabase();
// parse *ALL* your xml strings into a list of attribute name/values
List<AttrNameAndValue> attributeNamesAndValues = ParseForAttributeNamesAndValues(xmlContent);
// you can now easily bind this list of attribute names and values to a ListView, a GridView - whatever - try it!
}
private static List<string> GrabXmlStringsFromDatabase()
{
List<string> results = new List<string>();
// connection string - adapt to **YOUR SETUP** !
string connection = "server=(local);database=test;integrated security=SSPI";
// Query to get the XmlContent columns - I would **ALWAYS** recommend to have a WHERE clause
// to limit the number of rows returned from the query - up to you....
string query = "SELECT XmlContent FROM dbo.TestXml WHERE 1=1";
// set up connection and command for data retrieval
using(SqlConnection _con = new SqlConnection(connection))
using (SqlCommand _cmd = new SqlCommand(query, _con))
{
_con.Open();
// use a SqlDataReader to loop over the results
using(SqlDataReader rdr = _cmd.ExecuteReader())
{
while(rdr.Read())
{
// stick all XML strings into resulting list
results.Add(rdr.GetString(0));
}
rdr.Close();
}
_con.Close();
}
return results;
}
private static List<AttrNameAndValue> ParseForAttributeNamesAndValues(List<string> xmlContents)
{
// create resulting list of "AttrNameAndValue" objects
List<AttrNameAndValue> attributeNamesAndValues = new List<AttrNameAndValue>();
// loop over all XML strings retrieved from the database
foreach (string xmlContent in xmlContents)
{
// parse into an XDocument (Linq-to-XML)
XDocument xmlDoc = XDocument.Parse(xmlContent);
// find **ALL** attribute nodes
var nodeAttrs = xmlDoc.Descendants().Select(x => x.Attributes());
// loop over **ALL** atributes in each attribute node
foreach (var attrs in nodeAttrs)
{
foreach (var attr in attrs)
{
// stick name and value into the resulting list
attributeNamesAndValues.Add(new AttrNameAndValue { AttrName = attr.Name.LocalName, AttrValue = attr.Value });
}
}
}
return attributeNamesAndValues;
}
}
}

Categories

Resources