How to return SqlDataReader in C# WCF? - c#

In my C# WCF service I have a SqlDataReader that holds rows of data that I would like to return to the client.
How to return everything in the SqlDataReader?
Right now I have
if (sqlReader != null)
{
if (sqlReader.HasRows)
{
while (sqlReader.Read())
{
return sqlReader[0].ToString();
}
sqlConn.Close();
}
else
{
return null;
}
}
That only returns the first result. The return type of the class is string at the moment.
I was thinking of something like array in array, but I am not sure how?
EDIT:
Thanks for the many replies. I am interested in returning the entire SQL data that the service 'creates'. Not online the first column ([0]) - this was only for testing.
But I am not sure on how to get everything from the service back to the client.
How to return it?
For eg. in Powershell I would create a collection and add objects to that collection, if I had to pass it between clients.
I am looking for something similar in C# and WCF.
Many thanks so far :)
EDIT #2:
Got it! :)
Created a new class (e.g.):
public class ObjectNotification
{
public string AlertDescription;
public string Servername;
}
In my svc.cs file in top:
List<ObjectNotification> objlist = new List<ObjectNotification>();
And
if (sqlReader.HasRows)
{
while (sqlReader.Read())
{
ObjectNotification obj = new ObjectNotification();
obj.AlertDescription = sqlReader["AlertDescription"].ToString();
obj.Servername = sqlReader["ComputerName"].ToString();
objlist.Add(obj);
}
}
return objlist;
That gave me exactly what I wanted :)
Best regards

You need to define DataContract, If you pass list of strings or array of strings your service consumers need to know which index is for which column etc.. that approach will be hard when you adding or deleting column to the service in future. What you can do is create DataContract which having all the properties you need to send and create the operation contract accordingly. Now service consumers can update the service reference in future in case of changing field they will get compiler error. that is easy to identify.
public List<MyDataContract> GetData()
{
List<MyDataContract> list = new List<MyDataContract>();
//your code
if (sqlReader != null)
{
if (sqlReader.HasRows)
{
while (sqlReader.Read())
{
list.Add(new MyDataContract() {
Id = (int)sqlReader["Id"].ToString(),
Name= sqlReader = sqlReader["Name"].ToString() });
}
sqlConn.Close();
}
}
//finally return list of data
return list;
}
Sample Data Contract
[DataContract]
public class MyDataContract
{
[DataMember]
public int Id{ get; set; }
[DataMember]
public string Name{ get; set; }
}
And Operation contract
[OperationContract]
List<MyDataContract> GetData();
in my opinion we need more generic reusable code...
If you have only the .net Service consumers you can return DaTaSet or DataTable from the service method. You need to have SqlDataAdapter instead of sqlReader and Fill the DataTable Or Dataset and Return it. You can parace any number of columns no change in Service method definition. You can even send return type as string by using DataSet.GetXml()
Converting DataTable To Json
string json = JsonConvert.SerializeObject(table, Formatting.Indented);

// for instance
List<string> list = new List<string>();
if (sqlReader != null)
{
if (sqlReader.HasRows)
{
while (sqlReader.Read())
{
//return sqlReader[0].ToString();
list.Add(sqlReader[0].ToString());
}
sqlConn.Close();
}
else
{
return null;
}
}
return list; // ta-da

SqlReader is intended to work memory minimized and therefore will only query the result one by one. If you want to get all results you can use a while loop around your code to fetch rows as long as they are available. By calling return you will break the while loop and returning only first row. If you would call yield return an IEnumerable<string> would be returned by your method instead of string.

You can probably create an array or List and populate the List with your sqlReader.
List and Array can be serialized and transferred using WCF and are inter-operable too.
Inside the while loop
listObject.Add(sqlReader[0].ToString());

You need to actually read all the datareader rows by loading them into a datatable, the datareader is a lazyload object and can't be sent across the network since it's never fully populated at the begning:
DataTable dt=new DataTable();
dt.Load(dr);
Then, since you are using wcf, you possibly need to load that datatable into an object matching your contract interface. Take a look the snipet below that converts an arbitrary datatable into a well behaved object matching properties by using reflection.
Snippet usage
List<myType> r = (List<myType>) dt.ToList<myType>();
Snippet
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;
namespace xxxx.Sql.Extensions
{
public static class DataTableExtensions
{
/// <summary>
/// Gets a list of objects based on a generic datatable
/// </summary>
/// <typeparam name="T">List of objects</typeparam>
/// <param name="table">Existing datatable</param>
/// <returns></returns>
public static IList<T> ToList<T>(this DataTable table) where T : new()
{
IList<PropertyInfo> properties = typeof(T).GetProperties().ToList();
IList<T> result = new List<T>();
foreach (var row in table.Rows)
{
var item = CreateItemFromRow<T>((DataRow)row, properties);
result.Add(item);
}
return result;
}
private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
if (row.Table.Columns.Contains(property.Name))
{
var prop = row[property.Name] == System.DBNull.Value ? null : row[property.Name];
property.SetValue(item, prop, null);
}
}
return item;
}
/// <summary>
/// Creat a generic string list on the first field of a dataTable
/// </summary>
/// <param name="table"></param>
/// <returns></returns>
public static List<string> ToStringList(this DataTable table)
{
List<string> result = new List<string>();
foreach (DataRow dr in table.Rows)
result.Add(dr[0].ToString());
return result;
}
}
}

Ancient thread, but I'll add my two:
string sqltext = #"select a.columnown, a.columntwo, a.columnthreee from blahblah as a";
List<List<string>> toReturn = new List<List<string>>();
using (SqlConnection con = new SqlConnection("YourConnectionString"))
{
con.Open();
SqlCommand cmd = con.CreateCommand();
cmd.CommandText = sqlTest;
using (SqlDataReader sqlReader = cmd.ExecuteReader())
{
if (sqlReader != null)
{
if (sqlReader.HasRows)
{
while (sqlReader.Read())
{
List<string> innerList = new List<string>();
for (int i = 0; i < sqlReader.FieldCount; i++)
{
innerList.Add(sqlReader[i].ToString());
}
toReturn.Add(innerList);
}
con.Close();
}
}
}
}
then you just simply return your List<List<string>>

Related

Can't Convert list contain multiple class to DataTable

i am trying to convert a list to dataTable and then save it to the database , but i am facing a problem . I get an error that column Mapping does not match .
This is my List
public static class Program
{
static Logger _myLogger = LogManager.GetCurrentClassLogger();
public class Student
{
public int int { get; set; }
public string name { get; set; }
public string email { get; set; }
public string phoneNumber { get; set; }
public virtual ICollection<tblStudentCourses> tblStudentCourses { get; set; }
}
List<Student> student = new List<Student>();
This is the extensions that i am using
public static DataTable AsDataTable<T>(this IList<T> data)
{
DataTable dataTable = new DataTable(typeof(T).Name);
//Get all the properties
PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in Props)
{
//Defining type of data column gives proper data table
var type = (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(prop.PropertyType) : prop.PropertyType);
//Setting column names as Property names
dataTable.Columns.Add(prop.Name, type);
}
foreach (T item in data)
{
var values = new object[Props.Length];
for (int i = 0; i < Props.Length; i++)
{
//inserting property values to datatable rows
values[i] = Props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
//put a breakpoint here and check datatable
return dataTable;
}
This how i am calling the extension
using (var connection = new SqlConnection(ConfigurationManager.AppSettings["connectionString"]))
{
connection.Open();
SqlTransaction transaction = connection.BeginTransaction();
using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction))
{
bulkCopy.DestinationTableName = "dbo.Student";
bulkCopy.WriteToServer(student.AsDataTable());
connection.Close();
}
transaction.Commit();
}
The error :
The given ColumnMapping does not match up with any column in the source or destination
Use FastMember's ObjectReader to create an IDataReader on top of any collection, eg :
var student = new List<Student>();
...
using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(student, "Id", "Name", "Email","PhoneNumber"))
{
bcp.DestinationTableName = "SomeTable";
bcp.WriteToServer(reader);
}
SqlBulkCopy can use either a DataTable or IDataReader. ObjectReader.Create creates an object that wraps any collection and exposes it through an IDataReader interface that can be used with SqlBulkCopy.
It's also possible to use Linq-to-Dataset's CopyToDataTable or MoreLinq's ToDataTable extension methods to create a DataTable from an IEnumerable. These will have to read the entire IEnumerable though and cache all data in the DataTable. This can be expensive if there are a lot of rows.
ObjectReader on the other hand doesn't need to cache anything
The error The given ColumnMapping does not match up with any column in the source or destination
happen usually for 3 causes:
You didn't provide any ColumnMappings, and there is more column in the source than in the destination.
You provided an invalid column name for the source.
You provided an invalid column name for the destination.
In your case, you didn't supply column mapping. Here is an online example similar to your scenario: https://dotnetfiddle.net/WaeUi9
To fix it:
Provide a ColumnMappings
For example: https://dotnetfiddle.net/Zry2tb
More information about this error can be found here: https://sqlbulkcopy-tutorial.net/columnmapping-does-not-match
If you are able to read data in data table then change your code like below
bulkCopy.DestinationTableName = "dbo.Student";
bulkCopy.ColumnMappings.Add("<list field name>", "<database field name>");
//Map all your column as above
bulkCopy.WriteToServer(dataTable);
I hope this works for your problem.

Dynamic results using dapper in mvc

I am trying the below way to return the dynamic results using dapper and stored procedure. Am I doing it in correct way?
using (IDbConnection dbConnection = Connection)
{
dbConnection.Open();
var result = dbConnection.Query<dynamic>("LMSSP_GetSelectedTableData",
new
{
TableName = TableName,
LangaugeID = AppTenant.SelectedLanguageID,
UserID = AppTenant.UserID
}, commandType: CommandType.StoredProcedure).ToList();
if (result != null)
{
// Added just for checking the data
foreach (var item in (IDictionary<string, object>)result.FirstOrDefault())
{
string key = item.Key;
string value = item.Value.ToString();
}
}
}
What my stored procedure do is, I will pass any table name and based on that it will return the results/records.So, obviously my number of records, columns will be varied as per the table name passed.
To achieve this I have used dynamic keyword along with dapper.
So my question is how can I pass this data to view as a model and render the controls on the view as per the properties/column data type. Can I get the data type of column OR PropertyInfo?
But, when dapper retrieves the records from database it returns as dapper row type?
Using same SP to fetch data from different table would be confusing (not good design). However to solve your problem technically, you can create model having list of control information. Example of control information
public class ControlInformation
{
public string Name { get; set; }
public dynamic Value { get; set; }
public string ControlType { get; set; }
// Applicable for drop down or multi select
public string AllValues { get; set; }
}
Model will have list of ControlInformations
public List<ControlInformation> ControlInformations { get; set; }
View will render the controls (partial view based on control type) Ex: very basic case to render different view for int and another view for rest. I have 2 partial views "IntCtrl" and "StringCtrl".
#foreach (var item in Model.ControlInformations)
{
if (#item.ControlType == "System.Int32")
{
Html.RenderPartial("IntCtrl", item);
}
else
{
Html.RenderPartial("StringCtrl", item);
}
}
Hope this help.
Here we are calling method which returns Datatable :
public DataTable GetMTDReport(bool isDepot, int userId)
{
using (IDbConnection _connection = DapperConnection)
{
var parameters = new DynamicParameters();
parameters.Add("#IsDepot", isDepot);
parameters.Add("#userId", userId);
var res = this.ExecuteSP<dynamic>(SPNames.SSP_MTDReport, parameters);
return ToDataTable(res);
}
}
In this we can call stored procedures by calling our custom method "ExecuteSP" :
public virtual IEnumerable<TEntity> ExecuteSP<TEntity>(string spName, object parameters = null)
{
using (IDbConnection _connection = DapperConnection)
{
_connection.Open();
return _connection.Query<TEntity>(spName, parameters, commandTimeout:0 , commandType: CommandType.StoredProcedure);
}
}
and here is "DapperConnection" method to connect the database:
You can give connection string with key ["MainConnection"]
public class DataConnection
{
public IDbConnection DapperConnection
{
get
{
return new SqlConnection(ConfigurationManager.ConnectionStrings["MainConnection"].ToString());
}
}
}
And at last we call "ToDataTable" method to change our response in datatable . We will receive response in DapperRow from the database because we passsed dynamic type in stored procedure.
public DataTable ToDataTable(IEnumerable<dynamic> items)
{
if (items == null) return null;
var data = items.ToArray();
if (data.Length == 0) return null;
var dt = new DataTable();
foreach (var pair in ((IDictionary<string, object>)data[0]))
{
dt.Columns.Add(pair.Key, (pair.Value ?? string.Empty).GetType());
}
foreach (var d in data)
{
dt.Rows.Add(((IDictionary<string, object>)d).Values.ToArray());
}
return dt;
}

converting DataTable to List<Entity> (ProjectDracula)

I need a static method to convert DataTables(dynamic) to List(again dynamic Entity)
here is my code
help would be appereciated
public static ICollection<System.Data.Entity.Core.Objects.DataClasses.EntityObject> DtToEntity(DataTable DataTable,System.Data.Entity.Core.Objects.DataClasses.EntityObject EntityObject)
{
ICollection<System.Data.Entity.Core.Objects.DataClasses.EntityObject> _list = null;
System.Data.Entity.Core.Objects.DataClasses.EntityObject _tempClass;
foreach (DataRow dataRow in DataTable.Rows)
{
foreach(DataColumn dataColumn in DataTable.Columns)
{
foreach (var attribute in EntityObject.GetType().GetProperties())
{
if (attribute.Name == dataColumn.ColumnName && attribute.GetType().Equals(dataColumn.GetType()))
{
return _list;
}
}
}
}
private static List<T> ConvertDataTable<T>(DataTable dt)
{
List<T> data = new List<T>();
foreach (DataRow row in dt.Rows)
{
T item = GetItem<T>(row);
data.Add(item);
}
return data;
}
private static T GetItem<T>(DataRow dr)
{
Type temp = typeof(T);
T obj = Activator.CreateInstance<T>();
foreach (DataColumn column in dr.Table.Columns)
{
foreach (PropertyInfo pro in temp.GetProperties())
{
if (pro.Name == column.ColumnName)
pro.SetValue(obj, dr[column.ColumnName], null);
else
continue;
}
}
return obj;
}
Usage:
List< Student > studentDetails = new List<Student>();
studentDetails = ConvertDataTable<Student>(dt);
Source: http://www.c-sharpcorner.com/UploadFile/ee01e6/different-way-to-convert-datatable-to-list/
So basically you want use this structure for your mvc .net core app with dependency injection and ado.net for orm The problem that was first initiated this question was when using ado.net you have to map data table to c# object manually.But in extends of years it got grown and now you can easily implement this structure on your application which is dapper like functionality.I grant this is a small sized.But I think this is the fastest way you can actually get results from sql server.Personally you might use dapper which I also suggest to do.It is nice peace of code which I think deserve to go to wiki for future references
You start with repository which has db context factory injected into it. I used factory pattern for db context because you might want to use multiple instances of sql server on your application. Then factory pattern will be needed. I suggest create a small db for you basic information and make that factory singleton. In this manner a lot of time and effort of fetching data would be eliminated in the first place. In the first method of repo there is example functionality to showcase the design you so you use factory to fetch the result and cast it as object by covert data table function which was provided in previous answer(thank you very much #Nikhil Vartak) .So you have it!. In the further of the post I included convert data table functions to this post and which was main reason of this question. Others are parts of normal .net core or .net normality which is not concern of this post
/* repo */
public class repo
{
private readonly IDBContextFactory dBContextFactory;
public repo(IDBContextFactory _dbContextFactory)
{
_dbContextFactory=dBContextFactory;
}
public string GetLastRecord()
{
List< Student > studentDetails = new List<Student>();
studentDetails = ConvertDataTable<Student>(_dbCtextFactory.Select("mydb","select * from StudentDetail");
/*refrence from this post https://stackoverflow.com/questions/33515552/converting-datatable-to-listentity-projectdracula */;
}
}
/* interface of repo */
public interface IRepo
{
public string GetLastRecord();
}
/* controller */
public class mycontroller:BaseController
{
private readonly IRepo repo;
public mycontroller(IRepo _repo)
{
_repo=repo;
}
[httpGet]
public IActionResult<string> GetLastRecord()
{
return _repo.GetLastRecord();
}
}
/* some factory pattern for db context (multiple dbs) */
public class DBContextFactory
{
private SqlCommand BuildFactory(string dbName)
{
switch(dbName)
{
case 'mydb':
return CreateMyDB();
}
}
private SqlCommand CreateMyDB()
{
string connectionString = "your connection string";
SqlConnection connection =
new SqlConnection(connectionString));
SqlCommand command = new SqlCommand(connection);
return command.Open();
}
//Private SqlCommand GetMyOpenCommand()
public DataTable Select(string dbName,string query)
{
SqlDataAdapter dataAdapter=new SqlDataAdapter();
dataAdapter.SelectCommand=BuildFactory(dbName);
DataTable dt =new DataTable();
dataAdapter.Fill(dt);
con.Close();
return dt;
}
}
/* factory in dependncy pattern */
public inteface IDBContextFactory
{
SqlCommand BuildFactory(string dbName);
SqlCommand CreateMyDB();
DataTable Select(string dbName,string query)
}
/****** HERE IS YOUR GENERIC FILE ******/
private static List<T> ConvertDataTable<T>(DataTable dt)
{
List<T> data = new List<T>();
foreach (DataRow row in dt.Rows)
{
T item = GetItem<T>(row);
data.Add(item);
}
return data;
}
private static T GetItem<T>(DataRow dr)
{
Type temp = typeof(T);
T obj = Activator.CreateInstance<T>();
foreach (DataColumn column in dr.Table.Columns)
{
foreach (PropertyInfo pro in temp.GetProperties())
{
if (pro.Name == column.ColumnName)
pro.SetValue(obj, dr[column.ColumnName], null);
else
continue;
}
}
return obj;
}
/***** END OF GENERIC PART *****/
/* USAGE OF GENERIC */
List< Student > studentDetails = new List<Student>();
studentDetails = ConvertDataTable<Student>(dt);

Cannot implicitly convert type 'System.Collections.Generic.List<>' to method

I have a class GetSearchFilters_Results which has two lists:
[DataContract]
public class GetSearchFilters_Results
{
public List<ElementList> ElementList{ get; set; }
public List<Managers> ManagerList { get; set; }
}
I have a file called Service.cs:
public GetSearchFilters_Results GetSearchFilters(string DomainID)
{
//Main List return List
//List<GetSearchFilters_Results> SearchFilterResults = new List<GetSearchFilters_Results>();
//Class
GetSearchFilters_Results GSF = new GetSearchFilters_Results();
string cs = ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString;
try
{
using (SqlConnection con = new SqlConnection(cs))
{
con.Open();
SqlCommand cmd = new SqlCommand("spCPMapp_GetSearchFilters", con);
cmd.Parameters.AddWithValue("#Domain_Id", DomainID);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter sqlDa = new SqlDataAdapter(cmd);
DataSet Ds = new DataSet();
sqlDa.Fill(Ds);
DataTable DtWBS = new DataTable();
DataTable DTManager = new DataTable();
sqlDa.Fill(Ds);
DtWBS = Ds.Tables[0];
DTManager = Ds.Tables[1];
//Get WBS Elements List
if (DtWBS.Rows.Count > 0)
{
List<ElementList> ElementList= new List<ElementList>();
for (int i = 0; i < DtWBS.Rows.Count; i++)
{
ElementList wbs = new ElementList();
wbs.ProjectID = Convert.ToInt32(DtWBS.Rows[i]["Project_ID"].ToString());
wbs.WBSElementName = DtWBS.Rows[i]["WBSShort"].ToString();
WBSElementsList.Add(wbs);
//GSF.WBSElementsList.Add(wbs);
}
GSF.WBSElementsList = WBSElementsList;
//SearchFilterResults.Add(GSF);
}
//Get Managers List Start
if (DTManager.Rows.Count > 0)
{
List<Managers> ManagersList = new List<Managers>();
for (int i = 0; i < DTManager.Rows.Count; i++)
{
Managers Mgr = new Managers();
Mgr.TimeSheetID = Convert.ToInt32(DTManager.Rows[i]["Project_ID"].ToString());
Mgr.ManagerName = DTManager.Rows[i]["Manager"].ToString();
//GSF.ManagerList.Add(Mgr);
ManagersList.Add(Mgr);
}
GSF.ManagerList = ManagersList;
}
//Manager List End
}//Using End
//SearchFilterResults.Add(GSF);
}
catch (SqlException sqlEx)
{
sqlEx.ToString();
}
catch (FaultException ex)
{
ex.ToString();
}
catch (OverflowException e)
{
e.ToString();
}
return GSF.ManagerList; // I am getting error, how to add two lists into single objectGetSearchFilters_Results
}
And another class Elements:
[DataContract]
public class Elements
{
}
My questions are:
How can I add two separate lists into a single object?
I am getting an error if I add one list into the GetSearchFilter object that says:
Cannot implicitly convert type to System.Collection.Generic.List to System.Namespace.GetSearchFilterResult() method.
How do I fix this error?
Your problem is that you are returning a List and not a GetSearchFilter_Results.
If you intend to return a GetSearchFilter_Results object like the function header says, you can change your last line to:
return GSF;
Edit and side note:
You asked how to add two lists together. Assuming the lists are of the same object, you can use the AddRange() function like this:
List<string> stringListA = new List<string>();
List<string> stringListB = new List<string>();
stringListA.AddRange(stringListB); // stringListA now holds the elements from both A and B.
Another addition, hopefully I don't add too much
I've also noticed you don't differentiate how you catch each exception. I don't know what you need for your case, but you can eliminate several lines of code by simply saying:
catch(Exception e)
{
e.ToString();
}
Instead of multiple catch statements that all do the same thing.
You're posting an awful lot of code for this. Maybe try abstracting away a bit of your logic so it's cleaner and easier to read through?
Your problem is you're attempting to place a List<ManagerList> into a return of GetSearchFilters_Results. Though a List of ManagerList is a property of your GetSearchFilters_Results, they cannot be "implicitly converted" as the error states.
You would want to do something like this potentially:
[DataContract]
public class GetSearchFilters_Results
{
public List<ElementList> ElementList{ get; set; }
public List<Managers> ManagerList { get; set; }
public GetSearchFilters_Results
{
ElementList = new List<ElementList>();
ManagerList = new List<ManagerList>();
}
public GetSearchFilters_Results Execute()
{
this.ELementList = this.GetElementList();
this.ManagerList = this.GetManagerList();
return this;
}
public List<ElementList> GetElementList()
{
List<ElementList> list = new List<ElementList>();
// Get list information from db
return list;
}
public List<ManagerList> GetManagerList()
{
List<ManagerList> list = new List<ManagerList>();
// Get list information from db
return list;
}
}
You require the function
GetSearchFilters to return
GetSearchFilters_Results
but what you return really is
GSF.ManagerList
which is of type
List<Managers> ManagerList
That was your error. Anyway for the part 1 pf your question, you can create a class with two data members, each one is a list, then in the constructor or in a separated function, you pass two parameters each for list:
public Class TwoListsClass
{
List <type1> list1;
List <type2> list2;
public TwoListsClass (List <type1> list1, List <type2> list2)
{
this.list1 = list1;
this.list2 = list2;
}
}
then, when you finish the valuating of the two lists you can call the constructor or the function you wrote.
Wow, this is the older hard way to do things.... consider this: You already have two models.
public List<ElementList> ElementList{ get; set; }
public List<Managers> ManagerList { get; set; }
This tells me is could be generated from EF, which is good....
Now, there's a little known but very cool method in EF that looks like this:
public List<MyType> QueryThis(ViewModel vm)
{
using (var db = new MyEntities()){
var parms = GetParms(vm);
var query = Resources.QueryStrings.MyQuery;
var stuff = db.Database.SqlQuery<MyType>(query, parms);
return stuff.ToList();
}
Emphasis on the type being passed into the SQLQuery...
So all you really have to do is provide a new model that combines all the fields you want back and SQL will fill them in for you automatically. The class MyType will have all the fields from both models. SQL also knows how to populate base classes so you can inherit from one class and just enter in fields of the smaller class. No more parsing or even hitting the db more than once. Just tune up the query to get what you need in one shot.
All filtering from this point on can be done using LINQ... it's the new way to use the concepts of ADO.NET disconnected mode. When you dig into this a bit deeper you'll discover repositories, but that's for another day.
var filtered = MyTypeList.Where(p=>p.ManagerName == "SomeValue");
var filtered = MyTypeList.Where(p=>p.ElementType == "Somevalue");

Database name is not allowed with a table-valued parameter

I am getting following error when I call the Select function:
The incoming tabular data stream (TDS) remote procedure call (RPC)
protocol stream is incorrect. Table-valued parameter 3
("#SearchTableVar"), row 0, column 0: Data type 0xF3 (user-defined
table type) has a non-zero length database name specified. Database
name is not allowed with a table-valued parameter, only schema name
and type name are valid.
C# code
//DTO
public class SP_SearchEntity_Result
{
public string ID { get; set; }
public string NAME { get; set; }
}
//Businesslogic
public IQueryable Select(int PageIndex, int PageSize, List<KeyValuePair<string, string>> SearchBy, List<KeyValuePair<string, System.Data.SqlClient.SortOrder>> SortBy)
{
SqlDatabase obj = (SqlDatabase)DatabaseFactory.CreateDatabase();//System.Configuration.ConfigurationManager.ConnectionStrings["MySqlServer"].ConnectionString
return obj.ExecuteSprocAccessor<SP_SearchEntity_Result>("SP_SearchEntity", PageIndex, PageSize, SearchBy.ToDataTable(), SortBy.ToDataTable()).AsQueryable<SP_SearchEntity_Result>();
}
//Extension methods
public static DataTable ToDataTable(this List<KeyValuePair<string, string>> source)
{
DataTable dataTable = new DataTable("Test");
dataTable.Columns.Add("KEY",typeof(System.String));
dataTable.Columns.Add("VALUE", typeof(System.String));
foreach (KeyValuePair<string, string> data in source)
{
var dr = dataTable.NewRow();
dr["KEY"] = data.Key;
dr["VALUE"] = data.Value;
dataTable.Rows.Add(dr);
}
return dataTable;
}
public static DataTable ToDataTable(this List<KeyValuePair<string, System.Data.SqlClient.SortOrder>> source)
{
DataTable dataTable = new DataTable("Test");
dataTable.Columns.Add("KEY", typeof(System.String));
dataTable.Columns.Add("VALUE", typeof(System.String));
foreach (KeyValuePair<string, System.Data.SqlClient.SortOrder> data in source)
{
var dr = dataTable.NewRow();
dr["KEY"] = data.Key;
dr["VALUE"] = data.Value == System.Data.SqlClient.SortOrder.Ascending ? "ASC" : "DESC";
dataTable.Rows.Add(dr);
}
return dataTable;
}
The stored procedure returns two tables in result
SQL proc definition
CREATE TYPE KeyValueTableVariable AS TABLE
(
[KEY] NVARCHAR(800),
[VALUE] NVARCHAR(800)
)
GO
CREATE PROCEDURE SP_SearchEntity
#PageIndex INT=NULL,
#PageSize INT=NULL,
#SearchTableVar dbo.KeyValueTableVariable READONLY,
#SortTableVar dbo.KeyValueTableVariable READONLY
AS
BEGIN
/*Bla bla bla*/
SELECT '1' as [ID], 'Nitin' as [NAME]
SELECT '1' as [COUNT]
END
There are a number of requirements/limitations for passing Table Valued parameters to SQL Server. See e.g. the example under "Passing a Table-Valued Parameter to a Stored Procedure":
The code then defines a SqlCommand, setting the CommandType property to StoredProcedure. The SqlParameter is populated by using the AddWithValue method and the SqlDbType is set to Structured.
And note that just using AddWithValue is insufficient - the SqlDbType has to be changed to Structured.
I believe that the ExecuteSprocAccessor method isn't performing this change (or possibly, as in some other examples, where the TypeName has to be set to the name of the table type). I can't chase this all through the enterprise library source code, but since I can't find the word "Structured" anywhere in the solution, that's what leads me to this conclusion.
So, if you want to use TVPs, I think you have to abandon the Enterprise Library and write the data access code yourself using SqlClient types. (Since you're using TVPs, you're already abandoning the possibility of switching to a different RDBMS anyway).
I find that the xml datatype for stored proc parameters is easier to use. Rather than casting the parameters to DataTables, you would cast them to XML for the following example:
CREATE PROCEDURE SP_SearchEntity
#PageIndex INT=NULL,
#PageSize INT=NULL,
#SearchTableVar xml=NULL,
#SortTableVar xml=NULL
AS
BEGIN
/*Bla bla bla*/
SELECT '1' as [ID], 'Nitin' as [NAME]
SELECT '1' as [COUNT]
END
Here's a sample of the KeyValuePair and a query, after it is serialized as XML:
declare #sampleXml xml = '
<ArrayOfKeyValuePairOfstringstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
<KeyValuePairOfstringstring>
<key>foo</key>
<value>bar</value>
</KeyValuePairOfstringstring>
<KeyValuePairOfstringstring>
<key>hello</key>
<value>world</value>
</KeyValuePairOfstringstring>
</ArrayOfKeyValuePairOfstringstring>'
select
Node.Elem.value('*:key[1]', 'nvarchar(800)') as [Key]
,Node.Elem.value('*:value[1]', 'nvarchar(800)') as Value
from #sampleXml.nodes(N'/*:ArrayOfKeyValuePairOfstringstring/*:KeyValuePairOfstringstring') Node(Elem)
go
and a XML Serializer:
// from Plinqo: http://www.codesmithtools.com/product/frameworks
public static string ToXml<T>(this T item)
{
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
var sb = new System.Text.StringBuilder();
using (var writer = XmlWriter.Create(sb, settings))
{
var serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(writer, item);
}
return sb.ToString();
}
EDIT : Returning Multiple Result Sets and Binding them to objects
I'll show you how to do that, but I'm not sure it's what you want to do, based on your mock SQL. If you are really just returning a count of the objects that were returned, you can count your results after they are IQueryable.
First you'll need a way of binding the objects, which you can get by extending MVC. These model binders expect your query to return column names that match your model properties.
using System;
using System.Collections.Generic;
using System.Web.Mvc;
public partial class ModelBinder
{
/// <summary>
/// Binds the values of an Dictionary to a POCO model
/// </summary>
public virtual T BindModel<T>(IDictionary<string, object> dictionary)
{
DictionaryValueProvider<object> _dictionaryValueProvider = new DictionaryValueProvider<object>(dictionary, null);
return BindModel<T>(_dictionaryValueProvider);
}
/// <summary>
/// Binds the values of an IValueProvider collection to a POCO model
/// </summary>
public virtual T BindModel<T>(IValueProvider dictionary)
{
Type _modelType = typeof(T);
var _modelConstructor = _modelType.GetConstructor(new Type[] { });
object[] _params = new object[] { };
string _modelName = _modelType.Name;
ModelMetadata _modelMetaData = ModelMetadataProviders.Current.GetMetadataForType(() => _modelConstructor.Invoke(_params), _modelType);
var _bindingContext = new ModelBindingContext() { ModelName = _modelName, ValueProvider = dictionary, ModelMetadata = _modelMetaData };
DefaultModelBinder _binder = new DefaultModelBinder();
ControllerContext _controllerContext = new ControllerContext();
T _object = (T)_binder.BindModel(_controllerContext, _bindingContext);
return _object;
}
}
Example conventions for model binding:
public partial class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Project Project { get; set; }
public List<Person> Friends { get; set; }
}
public partial class Project
{
public int Id { get; set; }
public string Name { get; set; }
}
select
1 as [Id]
, 'NitinJs' as [Name]
, 5 as [Project.Id]
, 'Model Binding' as [Project.Name]
, 2 as [Friends[0]].Id]
, 'John' as [Friends[0]].Name]
, 3 as [Friends[1]].Id]
, 'Jane' as [Friends[1]].Name]
Now, you need a method that will read your Data results and bind them to a model:
/// <summary>
/// Reads a record from a SqlDataReader, binds it to a model, and adds the object to the results parameter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="reader"></param>
/// <param name="modelName"></param>
/// <param name="results"></param>
private void ReadAs<T>(SqlDataReader reader, string modelName, List<T> results, string commandText)
{
Dictionary<string, object> _result = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
string _key = modelName + "." + reader.GetName(i);
object _value = reader.GetValue(i);
if (_result.Keys.Contains(_key)) // Dictionaries may not have more than one instance of a key, but a query can return the same column twice
{ // Since we are returning a strong type, we ignore columns that exist more than once.
throw new Exception("The following query is returning more than one field with the same key, " + _key + ": " + commandText); // command.CommandText
}
_result.Add(_key, _value);
}
T _object = new ModelBinder().BindModel<T>(_result);
if (_object != null)
{
results.Add((T)_object);
}
}
Next, you need a way of getting an open connection to your Database (note: you'll probaly want to grab _dbConnectionString from your config):
public SqlConnection GetOpenConnection()
{
_sqlConnection = new SqlConnection(_dbConnectionString);
_sqlConnection.Open();
return _sqlConnection;
}
Finally, you need to connect to your database to get your result sets:
/// <summary>
/// Executes a SqlCommand that expects four result sets and binds the results to the given models
/// </summary>
/// <typeparam name="T1">Type: the type of object for the first result set</typeparam>
/// <typeparam name="T2">Type: the type of object for the second result set</typeparam>
/// <returns>List of Type T: the results in a collection</returns>
public void ExecuteAs<T1, T2>(SqlCommand command, List<T1> output1, List<T2> output2)
{
string _modelName1 = typeof(T1).Name;
string _modelName2 = typeof(T2).Name;
string _commandText = command.CommandText;
using (SqlConnection connection = GetOpenConnection())
{
using (command)
{
command.Connection = connection;
command.CommandTimeout = _defaultCommandTimeout;
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read()) // Call Read before accessing data.
{
ReadAs<T1>(reader, _modelName1, output1, _commandText);
}
reader.NextResult();
while (reader.Read()) // Call Read before accessing data.
{
ReadAs<T2>(reader, _modelName2, output2, _commandText);
}
} // end using reader
} // end using command
} // end using connection
}
Then your select method would look more like this:
public void SelectInto<SP_SearchEntity_Result, int>(int PageIndex, int PageSize, List<KeyValuePair<string, string>> SearchBy, List<KeyValuePair<string, System.Data.SqlClient.SortOrder>> SortBy, List<<SP_SearchEntity_Result> result1, List<int> result2)
{
SqlCommand command = new SqlCommand("SP_SearchEntity");
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.Add("PageIndex", SqlDbType.Int).Value = PageIndex;
command.Parameters.Add("SearchTableVar", SqlDbType.Xml).Value = SearchBy.ToXml();
List<KeyValuePair<string, string>> SortByCastToString = // modify your ToDataTable method so you can pass a List<KeyValuePair<string, string>> for SortBy
command.Parameters.Add("SortTableVar", SqlDbType.Xml).Value = SortByCastToString.ToXml();
ExecuteAs<SP_SearchEntity_Result, int>(command, result1, result2);
}
public void SomeCallingMethod()
{
List<SP_SearchEntity_Result> _results = new List<SP_SearchEntity_Result>{};
List<int> _counts = new List<int>{};
// ...
// setup your SearchBy and SortBy
// ...
SelectInto<SP_SearchEntity_Result, int>(1, 20, SearchBy, SortBy, _results, _counts);
}
TVPs as parameters to a stored procedure work for me just fine using Enterprise Library Data Access Application Block v6.0.1304. My C# code looks like this:
public static DataSet SomeHelperMethod(DataTable tvp1, DataTable tvp2)
{
DbCommand cmd = <SqlDatabase>.GetStoredProcCommand("StoredProcName");
SqlParameter p1 = new SqlParameter("#p1", tvp1);
p1.SqlDbType = SqlDbType.Structured;
cmd.Parameters.Add(p1);
SqlParameter p2= new SqlParameter("#p2", tvp2);
p2.SqlDbType = SqlDbType.Structured;
cmd.Parameters.Add(p2);
return <SqlDatabase>.ExecuteDataSet(cmd);
}

Categories