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);
}
Related
I am trying to pass 2 Guid's to a SQL Server table type and then execute a stored procedure.
My table type looks like this
CREATE TYPE [dbo].[Ident] AS TABLE
(
[Id] [uniqueidentifier] NOT NULL,
[Id2] [uniqueidentifier] NOT NULL
)
GO
My C# code looks like this. I am trying to pass id to id and value to id2 in SQL Server table type [Ident] and return [type] from the stored procedure.
I get this error
Failed to convert parameter value from a String[] to a IEnumerable`1.'
How do I pass these guids in the method to the table type?
class Program
{
static void Main(string[] args)
{
String connectionString = "connection string.....";
List<Guid> id = new List<Guid>();
id.Add(Guid.Parse("guid....."));
List<Guid> Value = new List<Guid>();
Value.Add(Guid.Parse("guid......."));
Console.WriteLine(GetValue2(id, Value, connectionString));
}
public static List<ItemTest> GetValue2(List<Guid> Id, List<Guid> Value, String connectionString)
{
List<ItemTest> items = new List<ItemTest>();
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand cmd = new SqlCommand("stored procedure....", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParameter = new SqlParameter();
tvpParameter.ParameterName = "#id";
tvpParameter.SqlDbType = System.Data.SqlDbType.Structured;
tvpParameter.Value = (new string[] { "Id", "Id2" });
tvpParameter.TypeName = "[dbo].[Ident]";
cmd.Parameters.Add(tvpParameter);
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
Console.WriteLine((int)rdr["type"]);
}
Console.ReadLine();
}
}
return items;
}
}
public class ItemTest
{
/// <summary>
/// Stock Item ID
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Item number as on channel
/// </summary>
public string Value { get; set; }
}
This MSDN article says you can make your parameter value a datatable (among other things), rather than a string array, perhaps something like:
var dt = new DataTable();
dt.Columns.Add("Id", typeof(Guid));
dt.Columns.Add("Id2", typeof(Guid));
dt.Rows.Add(guid1, guid2);
tvpParameter.Value = dt;
I'm not really sure what's going on with your List<T> all over the place; this example is for one pair of guids, guid1 and guid2. If you have multiple, put more guid pairs in the datatable (more rows)
Might be late answer. Hoping it would help for future visitors
Database.SqlQuery<MyEntityClassName>("stored procedure name #params, ....",
CreateGuidList("params name", list<guid>)).ToList();
public SqlParameter CreateGuidList(string paramname, List<Guid> ids){
DataTable table = new DataTable();
table.Columns.Add("Id", typeof(Guid));
if (ids != null)
{
foreach (var id in ids)
{
table.Rows.Add(id);
}
}
return new SqlParameter(name, table) { TypeName = "GuidList" };
}
I am getting columns from the DataTable at the runtime from the stored procedure. Code looks like this:
var parkDataTable = new DataTable("tmp");
...
SqlCommand cmd = new SqlCommand("FooStoredProcedure", db.Database.Connection as
SqlConnection, transaction.UnderlyingTransaction as SqlTransaction);
cmd.CommandType = CommandType.StoredProcedure;
dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
parkDataTable.Load(dr);
The interesting thing that my columns can have various names by adding date to a column name. For example, I have a column name 2017-09-01_FactPark, then the next time it can be 2017-10-01_FactPark. Please, see the following image with the columns:
I can know about column names and quantity of columns just at the runtime.
Is it possible to create a class with the above columns as properties at the runtime and project them to these properties like usual way:
public IQueryable<EmployeeDTO> GetEmployees()
{
return db.Employees
.Select(employee => new EmployeeDTO
{
ID = employee.ID,
FirstName = employee.PriceNumber,
LastName = employee.PriceDate
}).AsQueryable();
}
So my goal is to create class with properties from the DataTable and project DataTable columns to the class with properties.
Is it possible to create properties at the runtime and project DataTable columns to newly created properties of class?
One way to dynamically constitute the shape of your class is with DynamicObject class. You can create your own dynamic class which would set and fetch properties with their values.
For now, let's implement our dynamic class:
using System.Dynamic;
class Dynamo : DynamicObject
{
private Dictionary<string, object> items = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return items.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
items[binder.Name] = value;
return true;
}
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
string propName = indexes[0] as string;
items[propName] = value;
return true;
}
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
{
string propName = indexes[0] as string;
return items.TryGetValue(propName, out result);
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return items.Keys;
}
}
What we have here:
items dictionary. Allows us to store any value and fetch it by its name.
TryGetMember()/TrySetMember() methods. This group allows us to set/fetch values with Dot-Property notation: Class.Property.
TrySetIndex()/TryGetIndex() methods. This group allows us to set/fetch values with Indexer notation: Class[varHoldingPropName] or Class["propName"].
GetDynamicMemberNames() method. Allows to retrieve all properties names.
In order to set property names, you must use Indexer notation, because with Dot-Property notation the binder will use your property name as identifier name, while Indexer will evaluate your variable:
static void UseDynamicObject()
{
var colorProperty = "Color";
var ageProperty = "Age";
dynamic dynamo = new Dynamo();
dynamo.colorProperty = "red";
dynamo[ageProperty] = 20;
// DOT-PROPERTY NOTATION
// Error: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
// 'Dynamo' does not contain a definition for 'Color'.
Console.WriteLine(dynamo.Color);
// The property used to retrieve value "red" is "colorProperty" rather "Color".
Console.WriteLine(dynamo.colorProperty);
// INDEXER NOTATION
// We can use either variable or literal name of the property,
// so these two lines are equivalent.
Console.WriteLine(dynamo[ageProperty]);
Console.WriteLine(dynamo["Age"]);
}
The logic for app is following: you could fetch columns' names from SqlDataReader and use them for setting them on your dynamic object.
Here's one possible variant of using SqlDataReader and our dynamic class:
static void Main(string[] args)
{
var data = new List<dynamic>();
List<string> cols = default;
using (var conn = new SqlConnection(connStr))
{
conn.Open();
using (var comm = new SqlCommand("SELECT * FROM dbo.Client", conn))
{
using (var reader = comm.ExecuteReader())
{
// Get columns names
cols = Enumerable.Range(0, reader.FieldCount)
.Select(i => reader.GetName(i)).ToList();
while (reader.Read())
{
dynamic obj = new Dynamo();
cols.ForEach(c => obj[c] = reader[c]);
data.Add(obj);
}
}
}
}
/* OUTPUT DATA */
// To get columns names, you can:
// 1) use ready "cols" variable
// 2) use "GetDynamicMemberNames()" on first object:
// IEnumerable<string> cols = data[0].GetDynamicMemberNames();
foreach (var obj in data)
{
Console.WriteLine(String.Join(", ", cols.Select(c => $"{c} = {obj[c]}")));
}
// Output:
// ClientId = 1, ClientName = Client1
// ClientId = 2, ClientName = Client2
}
I want to dynamically generate a class based on the results from a query that user submits. For instance, if the user enters Select name, age from tbl, the result is a name column which is string and age which is an int. The resulting class should be:
public class Test
{
public string Name { get; set; }
public int Age { get; set; }
}
Is there an efficient way to do this via EntityFramework or features in C# or I have to use maybe reflection to create a new type and instantiate it.
PS: My purpose is to run this query on the database and show the results in a Grid to the user and run some filter/sort/etc. on it.
You could use TypeBuilder to create a new type and execute the query against database using EF's SqlQuery() as mentioned here.
OR
A cleaner method would be to use dynamic objects to bind the grid. Extend EF to return a collection of dynamic objects as suggested by ChristineBoersen here. The code was written before EF went to RTM. Here's a version that works:
public static class EFExtensions
{
public static IEnumerable<dynamic> CollectionFromSql(this DbContext dbContext, string Sql, Dictionary<string, object> Parameters)
{
using (var cmd = dbContext.Database.Connection.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;
}
}
You could invoke the above method as follows:
var results = context.CollectionFromSql("Select Name, Age from tbl", new Dictionary<string, object>()).ToList();
// Bind results to grid
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;
}
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>>