How to convert stored procedure result to entity - c#

I'm currently executing my stored procedure below, and it works perfectly. But I can't specify the command timeout.
var results = await _dbContext.DbContext.Database.SqlQuery<GetOutputDto>(#"[dbo].[GetOutput] " + parameterString, list.ToArray()).ToListAsync();
Now I've change this to the below, and wondering what's the best way to convert the result to an object. I have over 30 properties, so setting each value would be quite tedious. Was wondering if there's a clean solution as Entity Framework solution.
using (var conn = new SqlConnection(_dbContextProvider.DbContext.Database.Connection.ConnectionString))
{
conn.Open();
SqlCommand cmd = new SqlCommand(#"[dbo].[GetOutput]", conn);
cmd.CommandTimeout = 60;
cmd.CommandType = CommandType.StoredProcedure;
foreach (var item in list)
{
cmd.Parameters.Add(item);
}
cmd.ExecuteNonQuery();
cmd.Connection.Close();
// How to get the result to entity in a clean manner.
}

Using System.reflection in those situation is really handy.
public static List<T> Convert<T>(IDataReader dr) where T : class, new()
{
List<T> list = new List<T>();
T obj = default(T);
while (dr.Read()) {
obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties()) {
if (!object.Equals(dr[prop.Name], DBNull.Value)) {
prop.SetValue(obj, dr[prop.Name], null);
}
}
list.Add(obj);
}
return list;
}
using (var conn = new SqlConnection(_dbContextProvider.DbContext.Database.Connection.ConnectionString))
{
conn.Open();
SqlCommand cmd = new SqlCommand(#"[dbo].[GetOutput]", conn);
cmd.CommandTimeout = 60;
cmd.CommandType = CommandType.StoredProcedure;
foreach (var item in list)
{
cmd.Parameters.Add(item);
}
using ( var reader = cmd.ExecuteReader() ){
List<Entity> result = Convert<Entity>(reader); // convert to entity.
cmd.Connection.Close();
}
}

I would in all honesty send over as an array and convert to table type within SQL and do the dirty work on the server side. Also a good way to be able to specify the timeout can be done by either the connection strings within your config file or you can also pass that same parameter over to sql with a WAITFOR DELAY.
Cheers!

Not that hard, do it like this
note, this is lazy eval so it should perform well when there is user IO, still fairly fast in other cases, I've used it in data ETL projects with many records.
public static IEnumerable<dynamic>( /* params */)
{
// build command object here.
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read()) // read the first one to get the columns collection
{
var cols = reader.GetSchemaTable()
.Rows
.OfType<DataRow>()
.Select(r => r["ColumnName"]);
do
{
dynamic t = new System.Dynamic.ExpandoObject();
foreach (string col in cols)
{
((IDictionary<System.String, System.Object>)t)[col] = reader[col];
}
yield return t;
} while (reader.Read());
}
}
// remember to close connection
}
From my simple DB framework https://gist.github.com/hoganlong/b7f5c5e8dde61ae3cd6f

Related

Read all record in sql table using SqlDataReader

I want to read all records from "product" table and create objects from each records.
it only gets one records from the database, any ideas might help ?
public IReadOnlyList<Product> Search(string name)
{
var result = new List<Product>();
using (var conn = new SqlConnection(connectionString))
{
if (name == null)
{
var command = new SqlCommand("SELECT * FROM Product ", conn);
conn.Open();
using var reader = command.ExecuteReader();
{
while (reader.Read())
{
var prod = new Product((int)reader["ID"], (string)reader["Name"],
(double)reader["Price"], (int)reader["Stock"], (int)reader["VATID"],
(string)reader["Description"]);
result.Add(prod);
reader.NextResult();
}
reader.Close();
conn.Close();
return result;
};
}
}
If you have several result sets, you should loop over them, i.e. you should put one more outer loop, e.g.
using var reader = command.ExecuteReader();
do {
while (reader.Read()) {
var prod = new Product(
Convert.ToInt32(reader["ID"]),
Convert.ToString(reader["Name"]),
Convert.ToDouble(reader["Price"]), // decimal will be better for money
Convert.ToInt32(reader["Stock"]),
Convert.ToInt32(reader["VATID"]),
Convert.ToString(reader["Description"])
);
result.Add(prod);
}
}
while (reader.NextResult());
Note outer do .. while loop since we always have at least one result set.
You use NextResult which advances the reader to the next result set. This makes sense if you have multiple sql queries and you'd use it after the while-loop. Here it's just unnecessary and wrong.
You are already advancing the reader to the next record with Read.
If I get rid of it, this error occur : Unable to cast object of type
'System.DBNull' to type 'System.String.
You can use IsDBNull:
int nameIndex = reader.GetOrdinal("Name");
string name = reader.IsDBNull(nameIndex) ? null : reader.GetString(nameIndex);
int descIndex = reader.GetOrdinal("Description");
string description = reader.IsDBNull(descIndex) ? null : reader.GetString(descIndex);
var prod = new Product((int)reader["ID"],
name,
(double)reader["Price"],
(int)reader["Stock"],
(int)reader["VATID"],
description);
Use it for every nullable column, for the numeric columns you could use nullable types like int?.
You have an error in your code:
Remove the line reader.NextResult();
NextResult is used for moving to next result set not next record.
Definitely remove the NextResult(). That does NOT move between individual records in the same query. Read() does this for you already. Rather, NextResult() allows you to include multiple queries in the same CommandText and run them all in one trip to the database.
Try this:
public IEnumerable<Product> Search(string name)
{
using (var conn = new SqlConnection(connectionString))
using (var command = new SqlCommand("SELECT * FROM Product ", conn))
{
if (!string.IsNullOrEmpty(name) )
{
command.CommandText += " WHERE Name LIKE #Name + '%'";
command.Parameters.Add("#Name", SqlDbType.NVarChar, 50).Value = name;
}
conn.Open();
using var reader = command.ExecuteReader();
{
while (reader.Read())
{
var prod = new Product((int)reader["ID"], reader["Name"].ToString(),
(double)reader["Price"], (int)reader["Stock"], (int)reader["VATID"],
reader["Description"].ToString());
yield return prod;
}
}
}
}

Update using IDataParameter not working in Oracle

I have a C# abstract class for implementing database transactions that has both a SQL (System.Data.SqlClient) and Oracle (Oracle.ManagedDataAccess.Client) implementation.
public int ExecuteDML<T>(string sql, List<T> objects)
{
int cnt = 0;
using (IDbConnection conn = GetConnection())
{
using (IDbTransaction txn = conn.BeginTransaction())
{
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = PrepSQL(sql);
cmd.Transaction = txn;
try
{
foreach (T obj in objects)
{
cmd.Parameters.Clear();
foreach (var kvp in GetDbParameters<T>(obj))
{
IDataParameter param = new DbParameter
{
ParameterName = kvp.Key,
Value = kvp.Value ?? DBNull.Value
};
cmd.Parameters.Add(param);
}
cnt += cmd.ExecuteNonQuery();
}
txn.Commit();
}
catch (Exception)
{
txn.Rollback();
throw;
}
}
}
}
return cnt;
}
I am able to execute INSERT, UPDATE and DELETE statements in both implementations. But when I run an UPDATE in the Oracle implementation, the record does not get updated in the database; ExecuteNonQuery returns 0. However, the same data/command in the SQL implementation works fine.
Why would the parameterized query not work for UPDATE, while INSERT and DELETE are fine?
Query
UPDATE CONFIG_PARAMS SET PARAM_VALUE = :ParamValue, LOAD_DATE = :LoadDate, UPDATED_BY = :UpdatedBy WHERE ACTION_NAME = :ActionName AND PARAM_NAME = :ParamName
Found solution in this post. BindByName setting needs to be set explicitly for Oracle, since the parameters were out of order.
I added this code after creating the IDbCommand
if (cmd is Oracle.ManagedDataAccess.Client.OracleCommand)
{
((Oracle.ManagedDataAccess.Client.OracleCommand)cmd).BindByName = true;
}

Can I create an anonymous type collection from a DataReader?

I have this code:
var query = "SELECT * FROM Cats";
var conn = new SqlConnection(sqlConnectionString);
conn.Open();
var cmd = new SqlCommand(query);
var reader = cmd.ExecuteReader();
while (reader.Read())
{
var CatName = reader.GetString(0);
var CatDOB = reader.GetDateTime(1);
var CatStatus = reader.GetInt32(2);
}
I'd like to pull the rows out into an anonymous type collection, which I'd normally do using LINQ to iterate, but I an not sure if it's possible due to the way you have to call .Read() each time to get the next row.
Is there a way to do this?
You can create helper generic method and let compiler infer type parameter:
private IEnumerable<T> Select<T>(DbDataReader reader, Func<DbDataReader, T> selector)
{
while(reader.Read())
{
yield return selector(reader);
}
}
usage:
var items = SelectFromReader(reader, r => new { CatName = r.GetString(0), CarDOB = r.GetDateTime(1), CatStatus = r.GetInt32(2) });
You can even make the method an extension method on DbDataReader:
public static IEnumerable<T> Select<T>(this DbDataReader reader, Func<DbDataReader, T> selector)
{
while (reader.Read())
{
yield return selector(reader);
}
}
and use it like that:
var items = reader.Select(r => new { CatName = r.GetString(0), CarDOB = r.GetDateTime(1), CatStatus = r.GetInt32(2) });
Here is an example of doing it with dynamic (which I think is easier to work with) but some may feel does not adhere to the letter of your question.
Call it like this:
var result = SelectIntoList("SELECT * FROM Cats",sqlconnectionString);
You could (like I did) put it into a static class in a separate file for easier maintanence.
public static IEnumerable<dynamic> SelectIntoList(string SQLselect, string connectionString, CommandType cType = CommandType.Text)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandType = cType;
cmd.CommandText = SQLselect;
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read()) // read the first one to get the columns collection
{
var cols = reader.GetSchemaTable()
.Rows
.OfType<DataRow>()
.Select(r => r["ColumnName"]);
do
{
dynamic t = new System.Dynamic.ExpandoObject();
foreach (string col in cols)
{
((IDictionary<System.String, System.Object>)t)[col] = reader[col];
}
yield return t;
} while (reader.Read());
}
}
conn.Close();
}
}
}
It's possible, although not particularly neat. We'll need to create a new method that will allow us to create an empty sequence that allows for type inference off of a dummy value for starters:
public static IEnumerable<T> Empty<T>(T dummyValue)
{
return Enumerable.Empty<T>();
}
This lets us create a list of an anonymous type:
var list = Empty(new
{
CatName = "",
CatDOB = DateTime.Today,
CatStatus = 0
}).ToList();
(The item here isn't used.)
Now we can add our anonymous types to this list:
var cmd = new SqlCommand(query);
var reader = cmd.ExecuteReader();
while (reader.Read())
{
list.Add(new
{
CatName = reader.GetString(0),
CatDOB = reader.GetDateTime(1),
CatStatus = reader.GetInt32(2),
});
}
Of course, using a named type would likely be easier, so I would suggest using one unless there is a real compelling reason not to do so. That is especially true if you plan to use the list outside of the scope it's created in.
Technically, it may not answer your question, but simply don't use a reader. Instead use a SqlDataAdapter to Fill a DataSet, if you can. Take the 0th Table of that DataSet, and select a new anonymous object from the Rows collection.
using System.Data; // and project must reference System.Data.DataSetExtensions
var ds = new DataSet();
using (var conn = DbContext.Database.GetDbConnection())
using (var cmd = conn.CreateCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlText;
conn.Open();
(new SqlDataAdapter(cmd)).Fill(ds);
}
var rows = ds.Tables[0].AsEnumerable(); // AsEnumerable() is the extension
var anons = rows
.Select(r => new { Val = r["Val"] })
.ToList();

store the data coming from database in generic list

I am using generic list to store the data that comes by querying the databse.I uses List of classes actually for multiple rows.
But my problem is my classes have almost more than 20 properties and most of the time i uses only its 2 or 3 properties.
So I want to know that what is the best way to keep the data coming from database.
Below is my code
List<ImageGalleryCollection> tempList = new List<ImageGalleryCollection1>();
SqlConnection connection = Dal.GetConnection();
SqlParameter[] paramList = new SqlParameter[1];
paramList[0] = new SqlParameter("#cityId", cityId);
SqlDataReader data = Dal.ExecuteReaderSP(SPNames.GetRegCity, paramList, connection);
while(data.Read())
{
ImageGalleryCollection igc = new ImageGalleryCollection1();
igc.cityPhotoGalleryId = Convert.ToInt32(data["cityPhotoGalleryId"]);
igc.ImagePath = data["imagePath"].ToString();
tempList.Add(igc);
}
data.Close();
connection.Close();
return tempList;
In ImageGalleryCollection I have more that 20 properties and above i only uses two properties.I think it is very inefficient
Can you how your base class implementation? You can create another class with the most using attributes and use an object of that class inside your class.
IEnumerable<ImageGalleryCollection> GetImageGalleryCollection()
{
SqlConnection connection = Dal.GetConnection();
SqlParameter[] paramList = new SqlParameter[1];
paramList[0] = new SqlParameter("#cityId", cityId);
SqlDataReader data = Dal.ExecuteReaderSP(SPNames.GetRegCity, paramList,connection);
while(data.Read())
{
ImageGalleryCollection igc = new ImageGalleryCollection1();
igc.cityPhotoGalleryId = Convert.ToInt32(data["cityPhotoGalleryId"]);
igc.ImagePath = data["imagePath"].ToString();
yield return igc;
}
data.Close();
connection.Close();
}
I would like to suggest you to write a extension method for SqlDataReader and make use of the method in linq to fetch required columns from the returned rows of reader.
Extension method:
public static class DataReaderExtension
{
public static IEnumerable<Object[]> DataRecord(this System.Data.IDataReader source)
{
if (source == null)
throw new ArgumentNullException("source");
while (source.Read())
{
Object[] row = new Object[source.FieldCount];
source.GetValues(row);
yield return row;
}
}
}
using it in linq:
using (SqlConnection cn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("Select * from tblUser"))
{
cmd.CommandType = CommandType.Text;
cmd.Connection = cn;
cn.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
var result = (from row in dr.DataRecord()
select new
{
UserId = row[0],
UserName = row[1]
}).ToList();
}
}
}
This result list has only the required properties you select and helps to reduce the consumption of memory for unwanted properties.

foreach with SqlDataReader?

I have the following code:
SqlDataReader reader = getAddressQuery.sqlReader;
while (reader.Read())
{
foreach (Object ob in reader)
{
someText.InnerText = someText.InnerText + " " + ob.ToString();
}
}
The code in the foreach loop does not execute. However, I can do this:
SqlDataReader reader = getAddressQuery.sqlReader;
while (reader.Read())
{
someText.InnerText = reader[0].ToString();
}
Which works.
Obviously I could achieve the same result using a regular for loop rather than a foreach loop, but I think the foreach syntax is clearer, so I use it when possible.
What has gone wrong here? Are foreach loops in c# not as flexible as in more high level languages?
Something like the following. Note that IDataReader derives from IDataRecord which exposes the members used to process the current row:
IEnumerable<IDataRecord> GetFromReader(IDataReader reader)
{
while(reader.Read()) yield return reader;
}
foreach(IDataRecord record in GetFromReader(reader))
{
... process it ...
}
Or even something like the following, to get an enumeration or list of strongly-typed entity objects from a reader:
IEnumerable<T> GetFromReader<T>(IDataReader reader, Func<IDataRecord, T> processRecord)
{
while(reader.Read()) yield return processRecord(reader);
}
MyType GetMyTypeFromRecord(IDataRecord record)
{
MyType myType = new MyType();
myType.SomeProperty = record[0];
...
return myType;
}
IList<MyType> myResult = GetFromReader(reader, GetMyTypeFromRecord).ToList();
UPDATE in response to Caleb Bell's comment.
I agree Enumerate is a better name.
In fact in my personal "common" library, I've now replaced the above by an extension method on IDataReader:
public static IEnumerable<IDataRecord> Enumerate(this IDataReader reader)
{
while (reader.Read())
{
yield return reader;
}
}
And the caller can get strongly-typed objects using:
reader.Enumerate.Select(r => GetMyTypeFromRecord(r))
The foreach exposes an IDataRecord, which puts you in a very similar boat to the while loop:
using (SqlConnection conn = new SqlConnection(""))
using (SqlCommand comm = new SqlCommand("select * from somewhere", conn))
{
conn.Open();
using (var r = comm.ExecuteReader())
{
foreach (DbDataRecord s in r)
{
string val = s.GetString(0);
}
}
}
If you want to see something more useful, you'll need to have some of your own code that extracts the values from the record into something more custom, as the other answer has suggested.
Either way you are going to need custom code, whether you have it inline or not or use a while loop or not depends on how often it's going to be written I suppose, any more than once and you should probably stick it in a helper method somewhere.
And to answer the somewhat question: the problem is not the foreach, it is your attempted usage of what it returns for you, as your comparable use of the while loop is not actually comparable.
you may do also that...
string sql = "select * from Users";
using (SqlConnection conn = GetConnection()){
conn.Open();
using (SqlDataReader rdr = new SqlCommand(sql, conn).ExecuteReader()){
foreach (DbDataRecord c in rdr.Cast<DbDataRecord>()){
Console.Write("{0} {1} ({2}) - ", (string)c["Name"], (string)c["Surname"], (string)c["Mail"]);
Console.WriteLine((string)c["LoginID"]);
}
}
}

Categories