Creating dynamic model class with a loop - c#

I'm trying to create a controller that could get Stored Procedure result without column definition like without model.
I'm thinking that if I can get the column names, I can create a model for that and call Stored procedure with a model. But I could not create a model with looping. Is there any way to create a model with that idea.
Or do you have any idea to get result set without model from stored procedure?
I'm using oData library so it would be great if it can do that.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.OData;
namespace WebService.Controllers.OData.Common
{
public class CallSPWithoutColumnDefinitionController : ODataController
{
private SitContextTuborg db = new SitContextTuborg();
[EnableQuery]
//[SITAuthorize]
public IQueryable<CallSPWithoutColumnDefinitionModel> GetCallSPWithoutColumnDefinition()
{
Dictionary<string, string> parameterValues = new Dictionary<string, string>();
List<CallSPWithoutColumnDefinitionModel> ReturnValues = new List<CallSPWithoutColumnDefinitionModel>();
parameterValues.Add("STR_CO_NAME", "Pages");
var Results = db.ExecuteProcedureWithAuth<CallSPWithoutColumnDefinitionModel>("[Load-Co-Confıg.R01]", this.Request.GetClientIp(), parameterValues).ToList();
foreach (CallSPWithoutColumnDefinitionModel item in Results)
{
ReturnValues.Add(new CallSPWithoutColumnDefinitionModel()
{
LNG_ID = item.LNG_ID,
STR_COLL_NAME = item.STR_COLL_NAME,
STR_TYPE = item.STR_TYPE
});
}
return ReturnValues.AsQueryable();
}
public class tmpCallSPWithoutColumnDefinitionModel
{
//we need to create model with a for loop with Returnvalues' coll names above
}
}
}

private static Tuple<string, object[]> PrepareArguments(string storedProcedure, object parameters)
{
var parameterNames = new List<string>();
var parameterParameters = new List<object>();
if (parameters != null)
{
foreach (PropertyInfo propertyInfo in parameters.GetType().GetProperties())
{
string name = string.Format("#{0}", propertyInfo.Name);
object value = propertyInfo.GetValue(parameters, null);
parameterNames.Add(name);
parameterParameters.Add(new SqlParameter(name, value ?? DBNull.Value));
}
}
if (parameterNames.Count > 0)
storedProcedure = string.Format("{0} {1}", storedProcedure, string.Join(", ", parameterNames));
return new Tuple<string, object[]>(storedProcedure, parameterParameters.ToArray());
}
From: http://code-clarity.blogspot.in/2012/02/entity-framework-code-first-easy-way-to.html

You could use an ExpandoObject from System.Dynamic.
Here's a working example:
DataTable tbl = new DataTable();
tbl.Columns.Add(new DataColumn("hello", typeof(int)));
tbl.Columns.Add(new DataColumn("world", typeof(string)));
DataRow newRow = tbl.NewRow();
newRow["hello"] = 1;
newRow["world"] = "boobies";
tbl.Rows.Add(newRow);
foreach (DataRow row in tbl.Rows)
{
var expando = new ExpandoObject() as IDictionary<string, Object>;
foreach (DataColumn col in tbl.Columns)
{
expando.Add(col.ColumnName, row[col.ColumnName]);
}
}
What's important here is that when we call Add on that ExpandoObject, it's actually going to add those values as PROPERTIES on that object. Pretty nifty!

actually I think something is misunderstood. I have ReturnValues as you see in code as return value.. I need to send it to a model class below and I need to
CREATE new MODEL structure ;
something like
[Column("COLUMN1")] public string COLUMN1{ get; set; }
[Column("COLUMN2")] public string COLUMN2{ get; set; }
So I could not send it to class and create a model like above..

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

C# Easy drag and drop way to create DataTables from SQL?

When dealing with really small apps and the sqlbulkcopy, I normally create datatables by either using a FILL on an empty datatable OR I just type out something like this
DataTable dtGrps = new DataTable();
dtGrps.Columns.Add("objectGuid", typeof(Guid));
dtGrps.Columns.Add("DN", typeof(string));
dtGrps.Columns.Add("CN", typeof(string));
dtGrps.Columns.Add("groupType", typeof(string));
dtGrps.Columns.Add("description", typeof(string));
dtGrps.Columns.Add("whenCreated", typeof(string));
dtGrps.Columns.Add("whenChanged", typeof(string));
but it occurred to me that surly there is a built in way (non EF or Linq) to create all the code above by some drag in drop method. I mean I am using VS2017, surely MS has added this feature and I have just missed it is all.
So does this exist?
This is a very rough implementation, just intended as a starting point because my goal here isn't to write a new library. It could use a lot of optimization. But you can use strongly typed objects, use reflection to generate a DataTable based on the objects, and then use SqlBulkCopy to insert that.
using System;
using System.Collections.Generic;
using System.Data;
namespace StronglyStypedSqlBulkCopy
{
class Program
{
static void Main(string[] args)
{
List<Car> cars = GetSampleData();
DataTable dataTable = ConvertToDataTable(cars);
Console.WriteLine("Press any key to exit.");
Console.ReadKey(true);
}
public static List<Car> GetSampleData()
{
return new List<Car> {
new Car { Id = 1, Make = "Toyota", Model = "Tacoma", DateOfManufacture = DateTime.Now.AddDays(-1) },
new Car { Id = 2, Make = "Ford", Model = "Raptor", DateOfManufacture = DateTime.Now.AddDays(-2) },
new Car { Id = 3, Make = "Ram", Model = "1500", DateOfManufacture = DateTime.Now.AddDays(-3) }
};
}
public static DataTable ConvertToDataTable<T>(IEnumerable<T> objects)
{
var properties = objects.GetType().GetGenericArguments()[0].GetProperties();
var table = new DataTable();
foreach (var property in properties)
{
var columnName = property.Name; //may want to get from attribute also
//probably want to define an explicit mapping of .NET types to SQL types, and allow an attribute to specifically specify the SQL type
table.Columns.Add(columnName, property.PropertyType);
}
//probably want to cache the mapping from above in a real implementation
foreach (var obj in objects)
{
var row = table.NewRow();
foreach (var property in properties)
{
var columnName = property.Name; //may want to get from attribute also
var propertyValue = property.GetValue(obj);
row[columnName] = propertyValue;
}
table.Rows.Add(row);
}
return table;
}
}
public class Car
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public DateTime DateOfManufacture { get; set; }
}
}
Once you have a nice implementation of ConvertToDataTable, it's simply a matter of defining strongly typed classes, which are much easier to work with than raw DataTables.

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;
}

Drop SQLite tables using list of tables names

I am trying to drop a collection of tables by using tables names in the list and then get the type of each string and drop it :
List<string> models = new List<string> { "WebBrowser", "Notebook", "Members"};
foreach (string mod in models)
{
Type type = Type.GetType(mod));
using (var dbConn = new SQLiteConnection(app.DBPath))
{
dbConn.RunInTransaction(() =>
{
dbConn.DropTable<type>();
//dbConn.DropTable<WebBrowser>();
dbConn.Dispose();
dbConn.Close();
});
}
}
Problem : I can't drop table using this way, DropTable need the name of class (e.g WebBrowser ) and I don't want to drop each table alone (i.e dbConn.DropTable< WebBrowser >();) since I have more than 50 tables to drop.
Error : "The type or namespace name 'type' could not be found". ( and this error is expected since there is no Class 'type' in my Namespace .
You can drop tables using a SQL command in SQLite. All you need to do is iterate over your collection and build a SQL string each time, and execute it
List<string> models = new List<string> { "WebBrowser", "Notebook", "Members"};
foreach (string mod in models)
{
using (var dbConn = new SQLiteConnection(app.DBPath))
{
SQLiteCommand command = new SQLiteCommand(dbConn);
command.CommandText = string.Format("DROP TABLE {0};", mod);
command.ExecuteNonQuery();
}
}
I'm not sure if this syntax is exactly correct for your case (I only ever use sqlite-net in Windows 8.1) but the general approach is sound
You could create your own Extensionmethod like this:
public static class SQLiteConnectionExtensions
{
public static int DropTable(this SQLiteConnection conn, string tableName)
{
var query = string.Format("drop table if exists \"{0}\"", tableName);
return conn.Execute(query);
}
}
And then use it like this:
var tables = new List<string> { "WebBrowser", "Notebook", "Members" };
using (var dbConn = new SQLiteConnection(app.DBPath))
{
dbConn.RunInTransaction(() =>
{
foreach (string table in tables)
{
dbConn.DropTable(table);
}
});
}
You could also use Reflections. Here are the two extension methods:
public static void DropTable(this SQLiteConnection Connection, Type TableType)
{
typeof(SQLiteConnection).GetMethod("DropTable", Array.Empty<Type>())?.MakeGenericMethod(TableType).Invoke(Connection, null);
}
public static void DropTable(this SQLiteConnection Connection, Type[] AllTableTypes)
{
MethodInfo? Method = typeof(SQLiteConnection).GetMethod("DropTable", Array.Empty<Type>());
if (Method != null)
{
foreach (Type? OneTableType in AllTableTypes)
{
Method.MakeGenericMethod(OneTableType).Invoke(Connection, null);
}
}
}
You can call them on a SQLiteConnection object:
TheSqlLiteConnection.DropTable(typeof(SomeClass));
TheSqlLiteConnection.DropTable(new Type[] { typeof(SomeClass), typeof(SomeOtherClass) });

Categories