Alternative to reflection - c#

I have very less experiece with Generics and Reflection. What I assumed so for from the following sample is that it takes too much time to perform. Is there a way so that i accomplish the following without using reflection..
SCENARIO
I am working on a method which is generic. it takes an instance of a class passed to it and make SqlParameters from all of the properties. following is the code for generic method called "Store", and one more method that converts c# type to SqlDbType of DbType.
List<SqlParameter> parameters = new List<SqlParameter>();
public T Store<T>(T t)
{
Type type = t.GetType();
PropertyInfo[] props = (t.GetType()).GetProperties();
foreach (PropertyInfo p in props)
{
SqlParameter param = new SqlParameter();
Type propType = p.PropertyType;
if (propType.BaseType.Name.Equals("ValueType") || propType.BaseType.Name.Equals("Array"))
{
param.SqlDbType = GetDBType(propType); //e.g. public bool enabled{get;set;} OR public byte[] img{get;set;}
}
else if (propType.BaseType.Name.Equals("Object"))
{
if (propType.Name.Equals("String"))// for string values
param.SqlDbType = GetDBType(propType);
else
{
dynamic d = p.GetValue(t, null); // for referrences e.g. public ClassA obj{get;set;}
Store<dynamic>(d);
}
}
param.ParameterName = p.Name;
parameters.Add(param);
}
return t;
}
// mehthod for getting the DbType OR SqlDbType from the type...
private SqlDbType GetDBType(System.Type type)
{
SqlParameter param;
System.ComponentModel.TypeConverter tc;
param = new SqlParameter();
tc = System.ComponentModel.TypeDescriptor.GetConverter(param.DbType);
if (tc.CanConvertFrom(type))
{
param.DbType = (DbType)tc.ConvertFrom(type.Name);
}
else
{
// try to forcefully convert
try
{
param.DbType = (DbType)tc.ConvertFrom(type.Name);
}
catch (Exception e)
{
switch (type.Name)
{
case "Char":
param.SqlDbType = SqlDbType.Char;
break;
case "SByte":
param.SqlDbType = SqlDbType.SmallInt;
break;
case "UInt16":
param.SqlDbType = SqlDbType.SmallInt;
break;
case "UInt32":
param.SqlDbType = SqlDbType.Int;
break;
case "UInt64":
param.SqlDbType = SqlDbType.Decimal;
break;
case "Byte[]":
param.SqlDbType = SqlDbType.Binary;
break;
}
}
}
return param.SqlDbType;
}
To call my method suppose i have 2 classes as following
public class clsParent
{
public int pID { get; set; }
public byte[] pImage { get; set; }
public string pName { get; set; }
}
and
public class clsChild
{
public decimal childId { get; set; }
public string childName { get; set; }
public clsParent parent { get; set; }
}
and this is a call
clsParent p = new clsParent();
p.pID = 101;
p.pImage = new byte[1000];
p.pName = "John";
clsChild c = new clsChild();
c.childId = 1;
c.childName = "a";
c.parent = p;
Store<clsChild>(c);

If you want to get rid of reflection, you may find inspiration in the code below.
Here all access to objects to store in database as well as the sql property value assignment is handled by a runtime compiled expression build from the data type.
The table holding the values is assumed to be test and the field names are assumed to be identical to the property values.
For each property a Mapping<T> is constructed. It will hold a FieldName containing the database field, a SqlParameter which is supposed to be inserted correctly into a SQL INSERT statement (example in main) and finally if contains the compiled action, that can take an instance of the input T object and assign the value to the SqlParameters property Value. Construction of a collection of these mappings are done in the Mapper<T> class. Code is inlined for explanation.
Finally the main method shows how to bind the stuff together.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
namespace ExpTest
{
class Program
{
public class Mapping<T>
{
public Mapping(string fieldname, SqlParameter sqlParameter, Action<T, SqlParameter> assigner)
{
FieldName = fieldname;
SqlParameter = sqlParameter;
SqlParameterAssignment = assigner;
}
public string FieldName { get; private set; }
public SqlParameter SqlParameter { get; private set; }
public Action<T, SqlParameter> SqlParameterAssignment { get; private set; }
}
public class Mapper<T>
{
public IEnumerable<Mapping<T>> GetMappingElements()
{
foreach (var reflectionProperty in typeof(T).GetProperties())
{
// Input parameters to the created assignment action
var accessor = Expression.Parameter(typeof(T), "input");
var sqlParmAccessor = Expression.Parameter(typeof(SqlParameter), "sqlParm");
// Access the property (compiled later, but use reflection to locate property)
var property = Expression.Property(accessor, reflectionProperty);
// Cast the property to ensure it is assignable to SqlProperty.Value
// Should contain branching for DBNull.Value when property == null
var castPropertyToObject = Expression.Convert(property, typeof(object));
// The sql parameter
var sqlParm = new SqlParameter(reflectionProperty.Name, null);
// input parameter for assignment action
var sqlValueProp = Expression.Property(sqlParmAccessor, "Value");
// Expression assigning the retrieved property from input object
// to the sql parameters 'Value' property
var dbnull = Expression.Constant(DBNull.Value);
var coalesce = Expression.Coalesce(castPropertyToObject, dbnull);
var assign = Expression.Assign(sqlValueProp, coalesce);
// Compile into action (removes reflection and makes real CLR object)
var assigner = Expression.Lambda<Action<T, SqlParameter>>(assign, accessor, sqlParmAccessor).Compile();
yield return
new Mapping<T>(reflectionProperty.Name, // Table name
sqlParm, // The constructed sql parameter
assigner); // The action assigning from the input <T>
}
}
}
public static void Main(string[] args)
{
var sqlStuff = (new Mapper<Data>().GetMappingElements()).ToList();
var sqlFieldsList = string.Join(", ", sqlStuff.Select(x => x.FieldName));
var sqlValuesList = string.Join(", ", sqlStuff.Select(x => '#' + x.SqlParameter.ParameterName));
var sqlStmt = string.Format("INSERT INTO test ({0}) VALUES ({1})", sqlFieldsList, sqlValuesList);
var dataObjects = Enumerable.Range(1, 100).Select(id => new Data { Foo = 1.0 / id, ID = id, Title = null });
var sw = Stopwatch.StartNew();
using (SqlConnection cnn = new SqlConnection(#"server=.\sqlexpress;database=test;integrated security=SSPI"))
{
cnn.Open();
SqlCommand cmd = new SqlCommand(sqlStmt, cnn);
cmd.Parameters.AddRange(sqlStuff.Select(x => x.SqlParameter).ToArray());
dataObjects.ToList()
.ForEach(dto =>
{
sqlStuff.ForEach(x => x.SqlParameterAssignment(dto, x.SqlParameter));
cmd.ExecuteNonQuery();
});
}
Console.WriteLine("Done in: " + sw.Elapsed);
}
}
public class Data
{
public string Title { get; set; }
public int ID { get; set; }
public double Foo { get; set; }
}
}

I think you would in general benefit from using a standard ORM like NHibernate or Entity Framework. Both can do (customizable) mappings from classes to relational databases and NHibernate gives you full flexibility between all standard DBMS systems.
Having said that, you should be able to obtain some of the functionality by using Linq expressions, that can later be compiled; this should give you better performance.

Someone told you that reflection is really performance heavy, but you haven't really run your code through a profiler.
I tried your code, and it took 18 ms (65000 ticks) to run, I must say that it is fairly fast compared to the time it will take to save the data in the database.
But you are right about that it is really too much time.
I found that your code raised one Exception when it called tc.ConvertFrom when converting Byte[].
Removing the byte[] pImage from clsParent the runtime dropped to 850 ticks.
The performance problem here was the Exception, not the reflection.
I have taken the liberty to change your GetDBType to this:
private SqlDbType GetDBType(System.Type type)
{
SqlParameter param;
System.ComponentModel.TypeConverter tc;
param = new SqlParameter();
tc = System.ComponentModel.TypeDescriptor.GetConverter(param.DbType);
if (tc.CanConvertFrom(type))
{
param.DbType = (DbType)tc.ConvertFrom(type.Name);
}
else
{
switch (type.Name)
{
case "Char":
param.SqlDbType = SqlDbType.Char;
break;
case "SByte":
param.SqlDbType = SqlDbType.SmallInt;
break;
case "UInt16":
param.SqlDbType = SqlDbType.SmallInt;
break;
case "UInt32":
param.SqlDbType = SqlDbType.Int;
break;
case "UInt64":
param.SqlDbType = SqlDbType.Decimal;
break;
case "Byte[]":
param.SqlDbType = SqlDbType.Binary;
break;
default:
try
{
param.DbType = (DbType)tc.ConvertFrom(type.Name);
}
catch
{
// Some error handling
}
break;
}
}
return param.SqlDbType;
}
I hope this will help you in your quest.

Not an alternative, but only a suggestion: If the types are repeatedly stored during runtime you can try to tweak the reflection approach by introducing some caching.
Instead of having:
PropertyInfo[] props = (t.GetType()).GetProperties();
try following caching approach:
PropertyInfo[] props = GetProperties(type);
where GetProperties(Type) is a implemented like this:
private Dictionary<Type, PropertyInfo[]> propertyCache;
// ...
public PropertyInfo[] GetProperties(Type t)
{
if (propertyCache.ContainsKey(t))
{
return propertyCache[t];
}
else
{
var propertyInfos = t.GetProperties();
propertyCache[t] = propertyInfos;
return propertyInfos;
}
}
This is how you can cache the Type.GetProperties() method call. You can apply the same approach which such lookups for some other parts of the code. For instance the place where you use param.DbType = (DbType)tc.ConvertFrom(type.Name);. It is also possible to replace ifs and switchs with lookups. But before you do something like this you should really do some profiling. It complicates code a lot and you should not do that without a good reason.

There is no alternative to reflection unless using post-buil treatment to rewrite code.
You can use reflection without any performance issue if you use it only to prepare dynamic emitted type/method/delegate and include it naturally as a strategy pattern.

Related

Insert enum as string using Dapper.Contrib

I've just started using Dapper.Contrib to help me with inserts and gets but since my enums are stored as strings in the database (for several reasons) I'm having trouble with inserts. Dapper works seamlessly with string enums when reading, but inserts will always put the ordinal value into the database.
I've read many proposals to Dapper for that, and quite a few issues opened but didn't find a working solution. My simplified class looks like the following:
public class Person {
public long Id { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}
public enum Gender { Female, Male, TransWoman, TransMan }
I was expecting I could configure Dapper.Contrib to issue Inserts using enum names instead of ordinal values, so that the code bellow would magically work and insert 'Male' in the varchar(20) database field Gender:
void InsertPersonFelipe(SqlConnection conn) {
var person = new Person { Name = "Felipe", Gender = Gender.Male };
conn.Insert(person);
}
Is there a way to add custom mapping for typeof(Gender)?
Or, better yet, does Dapper.Contrib provides a configuration to make it use enum names instead of their ordinal values?
I've written an extension method to handle translating the enum into a string and takes into account the Table and Computed attributes in Dapper.Contrib. You could just as easily take these out if you didn't want to reference Dapper.Contrib.
Usage:
using (var sql = new SqlConnection(_connString))
{
sql.Open();
sql.InsertB(person);
}
Extension method:
public static long InsertB<T>(this SqlConnection sqlConnection, T obj)
{
Dictionary<string, object> propertyValuesMap = new Dictionary<string, object>();
var columns = new StringBuilder();
var values = new StringBuilder();
var tableName = ((TableAttribute)obj.GetType().GetCustomAttribute(typeof(TableAttribute))).Name;
var relevantProperties = obj.GetType().GetProperties().Where(x => !Attribute.IsDefined(x, typeof(ComputedAttribute))).ToList();
for (int i = 0; i < relevantProperties.Count(); i++)
{
object val = null;
var propertyInfo = relevantProperties[i];
if (propertyInfo.PropertyType.IsEnum)
{
val = Enum.GetName(propertyInfo.PropertyType, propertyInfo.GetValue(obj));
}
else
{
val = propertyInfo.GetValue(obj);
}
propertyValuesMap.Add(propertyInfo.Name, val);
var propName = i == relevantProperties.Count() - 1 ? $"{propertyInfo.Name}" : $"{propertyInfo.Name},";
columns.Append(propName);
values.Append($"#{propName}");
}
return sqlConnection.Execute($"Insert Into {tableName} ({columns}) values ({values})", propertyValuesMap);
}
I rewrote Dan's answer to be a little more modern C# and to not try to insert ID (because I had autoincrementing identity columns), as well as take a tablename instead of looking at attribute.
public static long InsertB<T>(this SqlConnection sqlConnection, T obj, string tableName)
{
Dictionary<string, object> propertyValuesMap = new Dictionary<string, object>();
var columnList = new List<String>();
var valueList = new List<String>();
var relevantProperties = obj.GetType().GetProperties().Where(x => !Attribute.IsDefined(x, typeof(ComputedAttribute))).ToList();
foreach (var propertyInfo in relevantProperties)
{
if (propertyInfo.Name.ToLower() == "id") continue; // do not try to insert id
var val = propertyInfo.PropertyType.IsEnum
? Enum.GetName(propertyInfo.PropertyType, propertyInfo.GetValue(obj))
: propertyInfo.GetValue(obj);
propertyValuesMap.Add(propertyInfo.Name, val);
columnList.Add(propertyInfo.Name);
valueList.Add($"#{propertyInfo.Name}");
}
return sqlConnection.Execute($"Insert Into {tableName} ({String.Join(", ", columnList)}) values ({String.Join(", ", valueList)})", propertyValuesMap);
}

Dapper building parameter list

I have this class:
public class Parameters
{
public string UserId {get;set;}
public string OrgId {get;set;}
public string Roles {get;set;}
}
It gets deserialised from a JSON string. So some of the properties are null.
What are the best ways to build up the params list to pass to Dapper.
At the moment my logic for building up the params string to tag on the end of the SQL statement goes like this :
var parameters = string.Empty;
var parametersObj = new { };
if (query.Parameters != null)
{
if (!string.IsNullOrWhiteSpace(query.Parameters.UserId))
{
parameters = string.Format("{0} UserId = #UserId", parameters);
// parametersObj.UserId =
}
if (!string.IsNullOrWhiteSpace(query.Parameters.OrganisationIdentifier))
{
parameters = string.Format("{0}, OrganisationIdentifier = #OrganisationIdentifier", parameters);
}
if (!string.IsNullOrWhiteSpace(query.Parameters.Roles))
{
parameters = string.Format("{0}, Roles = #Roles", parameters);
}
}
var sqlString = string.Format("exec {0} {1}", query.DbObjectName, parameters);
conn.QueryAsync<dynamic>(sqlString, )
As you can see with the parametersObj I was going with the JavaScript way of dynamically building an object. If I did do this with dynamic instead of an object - will it still work?
example:
var parameters = string.Empty;
dynamic parametersObj = new { };
if (query.Parameters != null)
{
if (!string.IsNullOrWhiteSpace(query.Parameters.UserId))
{
parameters = string.Format("{0} UserId = #UserId", parameters);
parametersObj.UserId = query.Parameters.UserId;
}
if (!string.IsNullOrWhiteSpace(query.Parameters.OrganisationIdentifier))
{
parameters = string.Format("{0} OrganisationIdentifier = #OrganisationIdentifier ", parameters);
parametersObj.OrganisationIdentifier= query.Parameters.OrganisationIdentifier;
}
if (!string.IsNullOrWhiteSpace(query.Parameters.Roles))
{
parameters = string.Format("{0} Roles = #Roles", parameters);
parametersObj.Roles= query.Parameters.Roles;
}
}
var sqlString = string.Format("exec {0} {1}", query.DbObjectName, parameters);
conn.QueryAsync<dynamic>(sqlString, parametersObj);
I think the second example will work when you change
dynamic parametersObj = new {};
to
dynamic parametersObj = new ExpandoObject();
and the query to
conn.QueryAsync(sqlString, new
{
UserId = parametersObj.UserId,
...
};
NOTE: filling in the dynamic object like
conn.QueryAsync(sqlString, parametersObj);
will raise the error
Extension methods cannot be dynamically dispatched
You don't need to do anything: just pass your object as the parameters. Dapper will only pass in properties/parameters that it can identify in the query... and even if it passed them all in: it understands null.
The object is fine.
...QueryAsync(sql, query.Parameters)...

How to assign value to property class when giving a object as parameter?

Sorry if the title does not reflect what I actually want.
I'm creating a generic class for selecting, updating, inserting and deleting dates from and to a database.
Basically, I want a function that gives me back an ObservableCollection<"can be anything"> ==> Where anything is a class and not strings. I would like to know if it is possible to do this, if yes, please,help me how I can achieve this.
this is my starting point:
//class a
public static ObservableCollection<ContactPerson> contactPersons = new ObservableCollection<ContactPerson>();
public static ObservableCollection<ContactPerson> getContactPerson()
{
contactPersons = (ObservableCollection<ContactPerson>)DBConnection.GetDataOutDatabase(typeof(ContactPerson), "Contactpersoon");
return contactPersons;
}
//class b
public static Object GetDataOutDatabase(Type myType,String table)
{
ObservableCollection<Object> objecten = new ObservableCollection<Object>();
string sql = "SELECT * FROM " + table;
DbDataReader reader = Database.GetData(sql);
while (reader.Read())
{
objecten.Add(Create(myType, reader));
}
return objecten;
}
private static Object Create(Type myType, IDataRecord record)
{
PropertyInfo[] myPropertyInfo = myType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < myPropertyInfo.Length; i++)
{
PropertyInfo myPropInfo = (PropertyInfo)myPropertyInfo[i];
String name = myPropInfo.Name;
Type type = myPropInfo.PropertyType;
}
return null;
}
And this is what I ultimately want to get. Is this possible?
//ContactPerson cp = new ContactPerson();
//cp.ID = (record["ID"].ToString());
//cp.Name = record["Name"].ToString();
//cp.Company = record["Company"].ToString();
//cp.JobTitle = new ContactPersonTitle()
//{
// Name = record["JobTitle"].ToString(),
//};
//cp.JobRole = new ContactPersonType()
//{
// Name = record["JobRole"].ToString(),
//};
//cp.City = record["City"].ToString();
//cp.Email = record["Email"].ToString();
//cp.Phone = record["Phone"].ToString();
//cp.Cellphone = record["Cellphone"].ToString();
Many thanks!
You can actually do this with reflection in generic methods.
public class DBConnection
{
public static ObservableCollection<T> GetDataOutDatabase<T>(string table)
{
var objecten = new ObservableCollection<T>();
string sql = "SELECT * FROM " + table;
DbDataReader reader = Database.GetData(sql);
while (reader.Read())
{
objecten.Add(Create<T>(reader));
}
return objecten;
}
public static T Create<T>(IDataRecord record)
{
var properties = typeof(T).GetProperties();
var returnVal = Activator.CreateInstance(typeof(T));
properties.ToList().ForEach(item =>
{
try
{
if (item.PropertyType.IsPrimitive)
{
item.SetValue(returnVal, Convert.ChangeType(record[item.Name].ToString(), item.PropertyType),null);
}
else
{
object[] parameters = {record};
var value =
typeof(DBConnection).GetMethod("Create").MakeGenericMethod(item.PropertyType).Invoke(null, parameters);
item.SetValue(returnVal,value,null);
}
}
catch
{
Write("Property Not Found");
}
});
return (T)returnVal;
}
}
The example above does assume that all properties names match the column names you are retrieving from your database communication. For instance in the ContactPersonTitle above rather than Name you would need to have JobTitle as the property name.
Not as you are currently doing it. You should look into the entity framework which allows translation of database tables datacollections.
have a look at:
http://www.codeproject.com/Articles/363040/An-Introduction-to-Entity-Framework-for-Absolute-B

Is there a better way to map Objects from a database query to an object?

I would like to know if there is a better way to solve this problem that I am overlooking. (I'm looking for a second opinion)
I want to create a generic and easy way to bind objects to database reader queries using "Oracle.DataAccess.Client".
In order to do this I initially wanted to create an object which inherited from OracleCommand; however, OracleCommand is a sealed object.
To deal with this I decided to create an extension method which attempts to map objects to generic columns in the database for each row.
EDIT : In my scenario, I know what the database will look like; however, I will not know where the database is before run time. i.e. The database may have been transferred ahead of time and the end user will specify the credentials for the database at run time.
Here is the implementation:
public static T[] Bind<T>(this OracleCommand oe, Binding binding, CommandBehavior Behavior = CommandBehavior.Default)
{
List<T> ret = new List<T>();
using (var reader = oe.ExecuteReader(Behavior))
{
while (reader.Read())
{
T unknownObj = (T)Activator.CreateInstance(typeof(T));
for (int i = 0; i < binding.GetBindCount(); i++)
{
var propinfo = unknownObj.GetType().GetProperties().ToList();
var prop = propinfo.Find((p) => p.Name == binding.GetBindValue(i, true));
prop.SetValue(unknownObj, reader[binding.GetBindValue(i, false)]);
}
ret.Add(unknownObj);
}
}
return ret.ToArray();
}
}
public class Binding
{
List<BindingMap> _map = new List<BindingMap>();
public void AddBind(String VariableName, String ColumnName)
{
_map.Add(new BindingMap(VariableName, ColumnName));
}
public String GetBindValue(int index, bool IsVariable = true)
{
var a = _map.ToArray();
return (IsVariable) ? a[index].Variable : a[index].Column;
}
public int GetBindCount()
{
return _map.Count;
}
}
public class BindingMap
{
public String Column;
public String Variable;
public BindingMap(String v, String c)
{
Variable = v;
Column = c;
}
}
Is there a better way to do this that I've overlooked, or is this a sound?
The way it would be used in real code is like this :
static void Main()
{
Binding b = new Binding();
b.AddBind("CreatedBy", "Create_by");
using (var Conn = new OracleConnection())
{
Conn.ConnectionString = od.Options.GetConnectionString();
using (var Command = new OracleCommand())
{
Command.Connection = Conn;
Command.CommandText = "Select * From Accounts";
Conn.Open();
var a = Command.Bind<Account>(b);
foreach (Account e in a)
{
Console.WriteLine(e.CreatedBy);
}
}
}
Console.Read();
}
public class Account
{
public String CreatedBy
{
get;
set;
}
}
As a slightly better way, you could designate the bound property like Telerik does: with a Linq expression. Here is the usage. Instead of :
AddBind("CreatedBy", "Created_by");
You would write
AddBind( x => x.CreatedBy, "Created_by");
You get a slightly stronger typing opportunity. The signature of AddBind would be:
public void AddBind<T>(Expression<Func<Account, T>> variable, string columnName) {
// ...
}
But I would not go into the way of generic functions. I'd rather overload a non-generic function :
public void AddBind(Expression<Func<Account, double>> variable, string columnName) {
// Add binding for double
}
public void AddBind(Expression<Func<Account, DateTime>> variable, string columnName) {
// Add binding for DateTime
}
// ...
The type of binding would then be selected according to the type of your mapped object. This prevents you from misnaming your properties, so you keep the possibility of performing name changes in the Account class without breaking your bindings.
The column name has still to be a string, sorry.
Of course, the way then to generalize is to make your BindingMap generic. (Taking your business class as a type parameter)
class BindingMap<BusinessClass> {
// ....
public void AddBind(Expression<Func<BusinessClass, double>> variable, string columnName) {
// Add binding for double
}
public void AddBind(Expression<Func<BusinessClass, DateTime>> variable, string columnName) {
// Add binding for DateTime
}
// ...
};
I leave as an exercice to you the problem of digging the property descriptor out of the expression :)

How can I map the results of a sql query onto objects?

Currently, I am using something like this:
try
{
dr = SQL.Execute(sql);
if(dr != null) {
while(dr.Read()) {
CustomObject c = new CustomObject();
c.Key = dr[0].ToString();
c.Value = dr[1].ToString();
c.Meta = dr[2].ToString();
customerInfo.CustomerList.Add(c);
}
}
else
{
customerInfo.ErrorDetails="No records found";
}
Instead of me doing the assigments manually, is there a way to do this mapping directly (assume that the column names match with the field names).
One requirement, however is that I want to do this by my current approach of using sql queries and not by using pure LINQ based approaches. For one, the SQL queries are big enough, involve complex JOINs and have been tested thoroughly so I don't want to introduce more bugs at the moment. Any suggestions?
One simple solution would be to make a constructor for your CustomObject that takes a DataRow (from the example, so if it's another class, please correct me).
And in your new constructor, do as you do in your own example.
public CustomObject(DataRow row)
{
Key = row[0].ToString();
// And so on...
}
One other way would be to introduce generics, and make a new function in your SQL-class
Example (Took code from Passing arguments to C# generic new() of templated type):
// This function should reside in your SQL-class.
public IEnumerable<T> ExecuteObject<T>(string sql)
{
List<T> items = new List<T>();
var data = ExecuteDataTable(sql); // You probably need to build a ExecuteDataTable for your SQL-class.
foreach(var row in data.Rows)
{
T item = (T)Activator.CreateInstance(typeof(T), row);
items.Add(item);
}
return items;
}
Example usage:
public IEnumerable<CustomObject> GetCustomObjects()
{
return SQL.ExecuteObject<CustomObject>("SELECT * FROM CustomObject");
}
I have tested this code in LinqPad, it should work.
You can achieve by creating a generic method for your requirement. Also you can make your new method as the extension for the data table.
public static List<T> ToList<T>(this DataTable table) where T : class, new()
{
try
{
List<T> list = new List<T>();
foreach (var row in table.AsEnumerable())
{
T obj = new T();
foreach (var prop in obj.GetType().GetProperties())
{
try
{
PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
propertyInfo.SetValue(obj, Convert.ChangeType(row[prop.Name], propertyInfo.PropertyType), null);
}
catch
{
continue;
}
}
list.Add(obj);
}
return list;
}
catch
{
return null;
}
}
}
Usage:
DataTable dtCustomer = GetCustomers();
List<CustomObject> CustomObjectList = dtCustomer.ToList<CustomObject>();
You should look into MicroORMs. Unlike regular ORMs, that provide an SDL you must use, MicroORMs allow you to use your own SQL queries and only provide the mapping from SQL result sets to C# objects and from C# objects to SQL parameters.
My favorite is PetaPoco, which also provides a query builder that uses your own SQL but does some neat manipulation of parameter numbers.
#user1553525's answer is great, however, if your column names do not match up exactly with your property names it does not work.
So first you would want to create a custom attribute. Then use the attribute in your class that you are trying to deserialize, finally, you want to deserialize the DataTable.
Custom Attribute
We create a custom attribute that will be applied to the properties inside of our class. We create the class to have the property Name that we will use later to get the correct column from our DataTable.
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class MySqlColName : Attribute
{
private string _name = "";
public string Name { get => _name; set => _name = value; }
public MySqlColName(string name)
{
_name = name;
}
}
Class to deserialize
Next, in the class that we are going to populate, we are going to declare the column names that will link to the properties in the class using the attribute [MySqlColName] that we just created.
However, if the property name is the same as the database column we do not need to specify the column name in an attribute because the .ToList<>() function will assume the name of the column from the properties name.
public class EventInfo
{
[MySqlColName("ID")]
public int EventID { get; set; }
//Notice there is no attribute on this property?
public string Name { get; set; }
[MySqlColName("State")]
public string State { get; set; }
[MySqlColName("Start_Date")]
public DateTime StartDate { get; set; }
[MySqlColName("End_Date")]
public DateTime EndDate { get; set; }
}
DataTable ToList Extension Method
Finally, we modify #user1553525's answer by adding in a check to see if our custom attribute has been provided. If it is then we set the name of the column to the name provided, otherwise, we use the property name (see code inside of the try block).
public static List<T> ToList<T>(this DataTable table) where T : class, new()
{
try
{
List<T> list = new List<T>();
foreach (var row in table.AsEnumerable())
{
T obj = new T();
foreach (var prop in obj.GetType().GetProperties())
{
try
{
//Set the column name to be the name of the property
string ColumnName = prop.Name;
//Get a list of all of the attributes on the property
object[] attrs = prop.GetCustomAttributes(true);
foreach (object attr in attrs)
{
//Check if there is a custom property name
if (attr is MySqlColName colName)
{
//If the custom column name is specified overwrite property name
if (!colName.Name.IsNullOrWhiteSpace())
ColumnName = colName.Name;
}
}
PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
//GET THE COLUMN NAME OFF THE ATTRIBUTE OR THE NAME OF THE PROPERTY
propertyInfo.SetValue(obj, Convert.ChangeType(row[ColumnName], propertyInfo.PropertyType), null);
}
catch
{
continue;
}
}
list.Add(obj);
}
return list;
}
catch
{
return null;
}
}//END METHOD
Usage
Finally, we can call the .ToList<>() method and get a list of serialized objects
List<EventInfo> CustomObjectList;
using (DataTable dtCustomer = GetDataTable("SELECT * FROM EventIndex"))
{
CustomObjectList = dtCustomer.ToList<EventInfo>();
}
Side Note: I have a few custom methods that I used
public static bool IsNullOrWhiteSpace(this string x)
{
return string.IsNullOrWhiteSpace(x);
}
public static DataTable GetDataTable(string Query)
{
MySqlConnection connection = new MySqlConnection("<Connection_String>");
try
{
DataTable data = new DataTable();
connection.Open();
using (MySqlCommand command = new MySqlCommand(Query, connection))
{
data.Load(command.ExecuteReader());
}
return data;
}
catch (Exception ex)
{
// handle exception here
Console.WriteLine(ex);
throw ex;
}
finally
{
connection.Close();
}
}
Assumption: if you need objects only for serialization or simple ad-hoc output.
You can use ExpandoObject and SqlDataReader.GetSchemaTable() like this:
private IEnumerable<dynamic> ReaderToAnonymmous(SqlCommand comm) {
using (var reader = comm.ExecuteReader()) {
var schemaTable = reader.GetSchemaTable();
List<string> colnames = new List<string>();
foreach (DataRow row in schemaTable.Rows) {
colnames.Add(row["ColumnName"].ToString());
}
while (reader.Read()) {
var data = new ExpandoObject() as IDictionary<string, Object>;
foreach (string colname in colnames) {
var val = reader[colname];
data.Add(colname, Convert.IsDBNull(val) ? null : val);
}
yield return (ExpandoObject)data;
}
}
}
Although there are posted faster solutions (i posted this as alternative lazy approach for ad-hoc SQL/Reader results/outputs).
The following function accepts a SQL string and an object, it requires the object to have a property for each column in the select statement. The object must be instantiated.
public object SqlToSingleObject(string sSql, object o)
{
MySql.Data.MySqlClient.MySqlDataReader oRead;
using (ConnectionHelper oDb = new ConnectionHelper())
{
oRead = oDb.Execute(sSql);
if (oRead.Read())
{
for (int i = 0; i < oRead.FieldCount; i++)
{
System.Reflection.PropertyInfo propertyInfo = o.GetType().GetProperty(oRead.GetName(i));
propertyInfo.SetValue(o, Convert.ChangeType(oRead[i], propertyInfo.PropertyType), null);
}
return o;
}
else
{
return null;
}
}
}
When searching for this answer I found that you can use Dapper library: https://dapper-tutorial.net/knowledge-base/44980945/querying-into-a-complex-object-with-dapper
You can use something like this:
using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();
IList<CustomObject> result = connection.Query<CustomObject>(sql, commandType: CommandType.Text).ToList();
}
Although this question has been around I could not find a clean solution to this. For my purpose I came up with the following which works quite well in my case.
using System.Dynamic;
private IEnumerable<ExpandoObject> GetQueryToList()
{
try
{
using (var conn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(MyQuery, conn))
{
var list = new List<ExpandoObject>();
conn.Open();
var reader = cmd.ExecuteReader();
while (reader.Read())
{
var expandoObject = new ExpandoObject();
for (var i = 0; i < reader.FieldCount; i++)
{
((IDictionary<string, object>) expandoObject).Add(
reader.GetName(i), reader[i]);
}
list.Add(expandoObject);
}
reader.Close();
return list;
}
}
catch (Exception ex)
{
var m = MethodBase.GetCurrentMethod();
Console.WriteLine(ex.Message + " " + m.Name);
}
return null;
}

Categories

Resources