Create properties of class and project to them at the runtime - c#

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
}

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.

Generate a class based on table schema

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

The type arguments for methods cannot be inferred from the usage. Try specifying the type arguments explicitly

I am trying to populate Autocomplete text from SQL Server in textbox using TextChanged() event. [WindowsForm, C#]
I have a partial class Form
TextChanged Event:
private void textBoxFilterCName_TextChanged(object sender, EventArgs e)
{
DataTable dt = FillCustomerName(textBoxFilterCName.Text);
List<string> listNames = CustomerName.ConvertDataTableToList(dt);
textBoxFilterCName.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
AutoCompleteStringCollection dataName = new AutoCompleteStringCollection();
dataName.AddRange(listNames.ToArray());
textBoxFilterCName.AutoCompleteCustomSource = dataName;
textBoxFilterCName.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
I am getting this error when I call this method CustomerName.ConvertDataTableToList().
The type arguments for methods 'CustomerName.ConvertDataTableToList(DataTable)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
FillCustomerName() Method
public DataTable FillCustomerName(string cName)
{
DataTable dtName = new DataTable();
List<string> listName = new List<string>();
try
{
dbconnection.Open();
string query = "SELECT [Name] FROM Customers WHERE Is_Active=1 AND Name like #CName + '%'";
using (SqlCommand cmd = new SqlCommand(query, dbconnection))
{
cmd.Parameters.Add("CName", SqlDbType.VarChar).Value = cName;
dtName.Load(cmd.ExecuteReader());
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Connection Error");
}
finally
{
dbconnection.Close();
}
return dtName;
}
CustomerName Class:
public static class CustomerName
{
private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
{
T item = new T();
foreach (var property in properties)
{
if (property.PropertyType == typeof(System.DayOfWeek))
{
DayOfWeek day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), row[property.Name].ToString());
property.SetValue(item, day, null);
}
else
{
property.SetValue(item, row[property.Name], null);
}
}
return item;
}
public static List<T> ConvertDataTableToList<T>(this DataTable table) where T : new()
{
IList<PropertyInfo> properties = typeof(T).GetProperties().ToList();
List<T> result = new List<T>();
foreach (var row in table.Rows)
{
var item = CreateItemFromRow<T>((DataRow)row, properties);
result.Add(item);
}
return result;
}
}
The query is executed and results are generated successfully when I debug the application. But I could not load the datatable results as list.
Possible suggestions for this conversion are most welcome.
So you want to convert your DataTable to a list of objects of a certain type. Right?
The generic method infers the generic type T from the parameters only. Since you have no parameters of type T then you need to explicitly specify it. So when you write this line:
List<string> listNames = CustomerName.ConvertDataTableToList(dt);
The Compiler can't infer what that the type T you want is a string because it is only used in the return type. So you need to explicitly specify that:
CustomerName.ConvertDataTableToList<string>(dt);
However, since you specify the condition where T : new(), this means the generic type should have a parameterless constructor, string doesn't satisfy that. You're matching the row by name, so your only option is to modify the generic methods or create a class like this:
public class CustomerNameHolder
{
public string Name { set; get; }
}
Then:
List<string> listNames = CustomerName.ConvertDataTableToList<CustomerNameHolder>(dt)
.Select(x=> x.Name).ToList();

Reading Data from database and storing the data in a List

I'm reading some data from a database (information on properties) and i store the information in Property objects. I put the objects in a list of type Property and i display the contents of the list in a listbox. However instead of getting different data for each object I'm getting the same data multiple times. Ps. I instantiate the Property object inside the while loop and the issue persists.
void fillListBoxWithProperties()
{
List<Property> propList = new List<Property>();
db_connection();
MySqlCommand cmd = new MySqlCommand();
cmd.CommandText = "select * from property ";
cmd.Connection = connect;
MySqlDataReader dr = cmd.ExecuteReader();
while(dr.Read())
{
Property prop = new Property();
prop.PropId = Convert.ToInt32(dr["propertyId"]);
prop.Address = dr["Address"].ToString();
prop.PropType = dr["PropertyType"].ToString();
propList.Add(prop);
}
foreach(Property details in propList)
{
lstBoxProperties.Items.Add(String.Format("{0} {1}", details.PropId, details.Address));
}
}
What the list box prints
If i add this code to the loop just for testing:
Property prop = new Property();
prop.PropId = Convert.ToInt32(dr["propertyId"]);
prop.Address = dr["Address"].ToString();
prop.PropType = dr["PropertyType"].ToString();
propList.Add(prop);
//added code
foreach (Property o in propList)
{
Console.WriteLine(o.toString());
}
The result in the console is the following:
Ballyare,
Rathmulan,
Rathmulan,
Letterkenny,
Letterkenny,
Letterkenny,
Convoy,
Convoy,
Convoy,
Convoy,
Gweedore,
Gweedore,
Gweedore,
Gweedore,
Gweedore,
Glenties,
Glenties,
Glenties,
Glenties,
Glenties,
Glenties,
Property Class Code:
class Property
{
private static int _propId =0;
private static string _address="";
private static string _propType="";
public Property()
{
}
public int PropId
{
get { return _propId; }
set { _propId = value; }
}
public string Address
{
get { return _address; }
set { _address = value; }
}
public string PropType
{
get { return _propType; }
set { _propType = value; }
}
}
Well, now after you added the Property class, it's quite clear why all of your instances seem to have the same content. You made your private members holding the data static. So they don't exist separately for each instance but only once for all instances of the class. Remove the static keyword (and see some introduction for what it does)

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