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);
}
Related
I have table called Asset. It has lot of columns. I only want to select two of them and use them separately.
Both of these columns are strings.
Linq query :
public static List<string> GetAssetIdsWithNames()
{
using (var db = DbManager.Get())
{
var result = db.Assets.SelectMany(i=> new[] { i.AssetName, i.AssetId }).Distinct().ToList();
return result;
}
}
Where I want to use them :
var assetList = AssetManager.GetAssetIdsWithNames();
//CURRENCYBOX IS A DROPDOWN
CurrencyBox.DataSource = assetList;
CurrencyBox.DataBind();
foreach (var item in assetList)
{
CurrencyBox.DataValueField = //asset id goes here
CurrencyBox.DataTextField =//asset name goes here
break;
}
You cannot access the anonymous type outside of the local scope.
Anonymous types can only be returned as Object outside their local scope and their properties inspected via reflection.
So in this scenario, you are likely better off to use a typed data contract and map from your Asset entity instead and then access it from your calling method.
Your use of SelectMany seems odd too, you probably are after Select instead.
public class AssetDto
{
public string Name { get;set; }
public string Id { get; set; }
}
public static List<AssetDto> GetAssetIdsWithNames()
{
using (var db = DbManager.Get())
{
var result = db.Assets.Select(i=> new AssetDto { Name = i.AssetName, Id = i.AssetId }).ToList();
return result;
}
}
You could use named value tuples for that so you don't need to create an extra class
public static List<(string Name, int Id)> GetAssetWithIds()
{
using (var db = DbManager.Get())
{
var result = db.Assets
.Select(a => new { a.AssetName, a.AssetId })
.Distinct().AsEnumerable()
.Select(a => (a.AssetName, a.AssetId))
.ToList();
return result;
}
}
You will need to add System.ValueTuple
Suppose I have this table:
How can I get the column name and database datatype from DbContext in Entity Framework Core?
Tips
The column with name clg# converted to clg1 by EF Core Scaffold tool so I need real column name not current EF name
I need database type, not clrType, of course the must be cross platform. Maybe I will change the database so the method must work too.
Desired result:
<D.clg#, int>
<D.clgname, nvarchar(50)>
<D.city, nvarchar(50)>
<D.pname, nvarchar(50)>
Can anyone provide a solution ?
Update (EF Core 3.x): Starting with EF Core 3.0, the metadata API has changed again - Relational() extensions have been removed, and properties have been replaced with Get and Set extension methods, so now the code looks like this:
var entityType = dbContext.Model.FindEntityType(clrEntityType);
// Table info
var tableName = entityType.GetTableName();
var tableSchema = entityType.GetSchema();
// Column info
foreach (var property in entityType.GetProperties())
{
var columnName = property.GetColumnName();
var columnType = property.GetColumnType();
};
Update (EF Core 2.x): Starting with EF Core 2.0, the things have changed, so the original answer does not apply anymore. Now EF Core builds separate model for each database type, so the code is much simpler and uses directly the Relational() extensions:
var entityType = dbContext.Model.FindEntityType(clrEntityType);
// Table info
var tableName = entityType.Relational().TableName;
var tableSchema = entityType.Relational().Schema;
// Column info
foreach (var property in entityType.GetProperties())
{
var columnName = property.Relational().ColumnName;
var columnType = property.Relational().ColumnType;
};
Original answer (EF Core 1.x):
Getting the access to the associated metadata is much easier in EF Core compared to EF - you start from DbContext.Model property to get IModel, use GetEntityTypes or FindEntityType to get IEntityType, then GetProperties or FindProperty to get IProperty etc.
However the problem is that EF Core allows you to use different setting fro different target database. In order to get the attributes corresponding to the current database used by the context, you need to get access to the IRelationalDatabaseProviderServices and use AnnotationProvider and TypeMapper properties to get the information needed.
Here is an example:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Storage;
public class DbColumnInfo
{
public string Name;
public string Type;
}
public static class RelationalDbHelpers
{
public static IEnumerable<DbColumnInfo> GetDbColums(this DbContext dbContext, Type clrEntityType)
{
var dbServices = dbContext.GetService<IDbContextServices>();
var relationalDbServices = dbServices.DatabaseProviderServices as IRelationalDatabaseProviderServices;
var annotationProvider = relationalDbServices.AnnotationProvider;
var typeMapper = relationalDbServices.TypeMapper;
var entityType = dbContext.Model.FindEntityType(clrEntityType);
// Not needed here, just an example
var tableMap = annotationProvider.For(entityType);
var tableName = tableMap.TableName;
var tableSchema = tableMap.Schema;
return from property in entityType.GetProperties()
let columnMap = annotationProvider.For(property)
let columnTypeMap = typeMapper.FindMapping(property)
select new DbColumnInfo
{
Name = columnMap.ColumnName,
Type = columnTypeMap.StoreType
};
}
}
For EF Core 5, you need to use the overload that accepts a StoreObjectIdentifier: GetColumnName(IProperty, StoreObjectIdentifier).
Update for EF 5:
var schema = entityType.GetSchema();
var tableName = entityType.GetTableName();
var storeObjectIdentifier = StoreObjectIdentifier.Table(tableName, schema);
var columnName = entityType.FindProperty(propertyName).GetColumnName(storeObjectIdentifier);
Based on EFC3.1 answer I have created this helper to store all table names and column names into an dictionaries of a singleton populated on first use so the code doesn't have to traverse everything again and again. We use it for NPGSQL bulk copy operations and it seems to work properly. This version does not filter out any properties from entity classes so be careful about ordering of fields when doing column name lists/strings. But then again as I understand it, you will get only properties that are mapped in context so everything might be ok.
The helper
public class ContextHelper
{
private readonly ILogger<ContextHelper> logger;
private readonly ApplicationDbContext context;
private static Dictionary<Type, string> tableNames = new Dictionary<Type, string>(30);
private Dictionary<Type, Dictionary<string, string>> columnNames = new Dictionary<Type, Dictionary<string, string>>(30);
public ContextHelper(ILogger<ContextHelper> logger, ApplicationDbContext context)
{
this.logger = logger;
this.context = context;
PopulateTableNames();
PopulateColumnNames();
}
private void PopulateTableNames()
{
logger.LogInformation("Populating table names in context helper");
foreach (var entityType in context.Model.GetEntityTypes())
{
tableNames.Add(entityType.ClrType, entityType.GetTableName());
}
}
private void PopulateColumnNames()
{
logger.LogInformation("Populating column names in context helper");
foreach (var entityType in context.Model.GetEntityTypes())
{
var clrType = entityType.ClrType;
if (!columnNames.ContainsKey(clrType))
{
columnNames.Add(clrType, new Dictionary<string, string>(30));
}
foreach (var property in entityType.GetProperties())
{
columnNames[clrType].Add(property.Name, property.GetColumnName());
}
}
}
public string GetTableName<T>()
{
return context.Model.FindEntityType(typeof(T)).GetTableName();
}
public string GetColumnName<T>(string propertyName)
{
return columnNames[typeof(T)][propertyName];
}
public List<string> GetColumnNames<T>()
{
return columnNames[typeof(T)].Select(x => x.Value).ToList();
}
}
Startup registration
services.AddSingleton<ContextHelper>();
Usage, something along these lines
var columnNames = contextHelper.GetColumnNames<OvenEventLog>().Where(x=>x != contextHelper.GetColumnName<OvenEventLog>(nameof(OvenEventLog.IdLog)));
var separatedCN = string.Join(", ", columnNames);
using (var writer = conn.BeginBinaryImport(
$"COPY {contextHelper.GetTableName<OvenEventLog>()} ({separatedCN}) FROM STDIN (FORMAT BINARY)")
For those who arrived here, but are not using .NET CORE, like me.
try this:
public partial class MyDbContext : System.Data.Entity.DbContext
{
public string GetTableName(Type entityType)
{
var sql = Set(entityType).ToString();
var regex = new Regex(#"FROM \[dbo\]\.\[(?<table>.*)\] AS");
var match = regex.Match(sql);
return match.Groups["table"].Value;
}
public string[] GetColumnName(Type entityType)
{
var strs = new List<string>();
var sql = Set(entityType).ToString();
var regex = new Regex(#"\[Extent1\]\.\[(?<columnName>.*)\] AS");
var matches = regex.Matches(sql);
foreach (Match item in matches)
{
var name = item.Groups["columnName"].Value;
strs.Add(name);
}
return strs.ToArray();
}
}
maybe redundante, but it saves time.
antonio
I created and used this function to do the exact thing on EF Core 5.0
public static List<ColumnInfo> GetAllColumns(this DbContext db, Type TableName)
{
var entityType = db.Model.FindEntityType(TableName);
var properties = entityType.GetProperties();
List<ColumnInfo> columns = new List<ColumnInfo>(properties.Count());
foreach (var property in properties)
columns.Add(new ColumnInfo(property.GetColumnName(StoreObjectIdentifier.SqlQuery(entityType)), property.GetColumnType()));
return columns;
}
public class ColumnInfo
{
public string Name;
public string Type;
public ColumnInfo(string Name, string Type)
{
this.Name = Name;
this.Type = Type;
}
}
you can also replace string Type to SqlDbType Type and so, the foreach line would be like this:
columns.Add(new ColumnInfo(
property.GetColumnName(StoreObjectIdentifier.SqlQuery(entityType)),
Enum.Parse<SqlDbType>( property.GetColumnType().Slice(0,"(",true), true)));
where Slice is my function, there's a necessary piece of code:
public static string Slice(this string s, int Start, string EndsWith, bool AlwaysReturnString = false)
{
var end =s.LastIndexOf(EndsWith);
if (end < 0) return AlwaysReturnString? s : null;
if (Start > end) throw new ArgumentException($"start ({Start}) is be bigger than end ({end})");
return s.Slice(Start, end);
}
public static int IndexOfEnd(this string s, string s2)
{
if (s == null)
if (s2.Length == 0)
return 0;
int i = s.IndexOf(s2);
return i == -1 ? -1 : i + s2.Length;
}
public static string Slice(this string s, int Start = 0, int End = Int32.MaxValue)
{
if (Start < 0) throw new ArgumentOutOfRangeException($"Start is {Start}");
if (Start > End) throw new ArgumentException($"start ({Start}) is be bigger than end ({End})");
if (End > s.Length) End = s.Length;
return s.Substring(Start, End - Start);
}
I want to replace words in a string that matches a keywords stored in an array with data from matching column in a table.
My model is People
public class People()
{
public int Id { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
The method i have tried is :
public void ProcessString(string message)
{
using (DBEntities db = new DBEntities())
{
var people = db.People.ToList();
foreach(var person in people)
{
string[] keyword = {"#Title", "#Name", "#Surname"};
for (int i=0; i<keyword.Length; i++)
{
string updatedString = Regex.Replace(body, keyword[i], matchingcolumndata);
}
}
}
So instead of matchingcolumndata in the string updatedString = Regex.Replace(body, keyword[i], matchingcolumndata); line i want to put the data that is matching the column in my table People. Please help with the question if its not straight to the point.
From my understand it may be you are looking for
foreach(var person in people)
{
string[] keyword = {"#Title", "#Name", "#Surname"};
body= body.Replace(keyword[0],person.Title);
body= body.Replace(keyword[1],person.Name);
body= body.Replace(keyword[2],person.Surname);
}
It doesn't make any sense to declare your keywords array inside the loop. If you want to do this in a neat way (and subject to modifications easily), you can create a map between the keyword and the property of the Person. Like this:
using (DBEntities db = new DBEntities())
{
var people = db.People.ToList();
// maps each keyword to a property in the Person class
var keywordPropertyMapping = new Dictionary<string, Func<Person, string>>()
{
{ "#Title", p => p.Title },
{ "#Name", p => p.Name },
{ "#Surname", p => p.Surname }
};
foreach (var person in people)
{
foreach(var keywordFunc in keywordPropertyMapping)
{
body = body.Replace(keywordFunc.Key, keywordFunc.Value(people));
}
}
}
For any new keyword, you just add one simple line inside the dictioanry keywordPropertyMapping, and the magic works.
If you're not familiar with Func<T, TResult> which is one critical part of the Linq magic, then read the Docs:
Encapsulates a method that has one parameter and returns a value of
the type specified by the TResult parameter.
You can use this delegate to represent a method that can be passed as
a parameter without explicitly declaring a custom delegate. The
encapsulated method must correspond to the method signature that is
defined by this delegate. This means that the encapsulated method must
have one parameter that is passed to it by value, and that it must
return a value.
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
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;
}