Revisiting IEnumerable to DataTable extension method -- Issue with strings - c#

I am working on a "ToDataTable()" that I can use on IEnumerable. Lots of examples of that, such as here: Convert IEnumerable to DataTable. DataTables are desirable for me because I am coming from a Foxpro realm where we are used to cursors. So, I dig DataTables because they are relatively simple, easy to work with, and can house decent column meta-data. And I've already got a bunch of code that can display them nicely (with sort and filter grid headers), export to Excel, etc.
Someone made a really good comment on the thread I linked to above: "This doesn't work if the type is string as the properties are chars and length but the get value then tries to put the full string into the char column."
Problem is, certain things fit the extension method call (like a one-dimensional array of strings) but then throw errors when the conversion takes place. I wrote an extension method that adds IsValueType() to the mix, but the problem is that returns false for strings. Finally, I added two kludges: a parameter to force the item in the sequence to be treated as a value, and to treat the item in the sequence as a value if it is found to be of type "String".
Is there a better way of doing this, or are strings just an odd man out due to their unique breed of typing and reflection results? The following code works with every IEnumerable I can think of, and it works with one-dimensional arrays for all DataTable column types except for string (well, it works for strings with the hard-coded kludge, but would error out otherwise). It's not a big deal to hard-code the string scenario, I just wish there was a more elegant way to do this. Any thoughts?
public static DataTable ToDataTable<T>(this IEnumerable<T> items, string tableName = "", bool treatItemAsValue = false)
{
// We want a single extension method that can take in an enumerable sequence (such as a LINQ query)
// and return the result as a DataTable. We want this to be a one stop shop for converting
// various objects into DataTable format, as DataTables are a nice parallel to Foxpro cursors.
if (items == null) { return null; }
Type itemType = typeof(T);
bool typeIsNullable = itemType.IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>));
string itemTypeName = "";
bool typeIsValue = false;
Type itemUnderlyingType = itemType;
if (typeIsNullable)
{
// Type of enumerable item is nullable, so we need to find its base type.
itemUnderlyingType = Nullable.GetUnderlyingType(itemType);
}
typeIsValue = itemUnderlyingType.IsValueType;
itemTypeName = itemUnderlyingType.Name;
DataTable dt = new DataTable();
DataColumn col = null;
if ((treatItemAsValue) || (itemTypeName == "String"))
{
// We have been asked to treat the item in the sequence as a value, of the items
// in the sequence are strings. Strings are NOT considered a value type in regards
// to IsValueType(), but when item values are assessed, it will be the value
// of the string that tries to pull in.
typeIsValue = true;
}
if (itemTypeName == "DataRow")
{
// Special case. If our enumerable type is DataRow, then we can utilize a more appropriate
// (built-in) extension method to convert enumerable DataRows to a DataTable.
dt = ((IEnumerable<DataRow>)items).CopyToDataTable();
}
else
{
// We must have an enumerable sequence/collection of some other type, possibly anonymous.
// Get properties of the enumerable to add as columns to the data table.
if (typeIsValue)
{
// Our enumerable items are of a value type (e.g. integers in a one-dimensional array).
col = dt.Columns.Add();
col.AllowDBNull = typeIsNullable;
col.ColumnName = itemTypeName;
col.DataType = itemUnderlyingType;
// Now walk through the enumeration and add rows to our data table (single values).
foreach (var item in items)
{
dt.Rows.Add(item);
}
}
else
{
// The type should be something we can walk through the properties of in order to
// generate properly named and typed columns of our DataTable.
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
Type propType = prop.PropertyType;
// Is it a nullable type? Get the underlying type.
if (propType.IsGenericType && propType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
propType = new NullableConverter(propType).UnderlyingType;
}
dt.Columns.Add(prop.Name, propType);
}
// Now walk through the enumeration and add rows to our data table.
foreach (var item in items)
{
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dt.Rows.Add(values);
}
}
}
// Give the DataTable a reasonable name.
if (tableName.Length == 0)
{
if (typeof(T).IsAnonymous())
{
// Anonymous types have really goofy names, so there is no use using that as table name.
tableName = "Anonymous";
}
else
{
// This is NOT an anonymous type, so we can use the type name as table name.
tableName = typeof(T).Name;
}
}
return dt;
}

Well, no responses except a comment that pretty much missed the point, so I'll just post what I ended up with. This final version also accounts for items with no properties (such as "Object" itself), and does some better handing of null items/values:
public static DataTable ToDataTable<T>(this IEnumerable<T> items, string tableName = "", bool treatItemAsValue = false)
{
// We want a single extension method that can take in an enumerable sequence (such as a LINQ query)
// and return the result as a DataTable. We want this to be a one stop shop for converting
// various objects into DataTable format, as DataTables are a nice parallel to Foxpro cursors.
if (items == null) { return null; }
Type itemType = typeof(T);
bool typeIsNullable = itemType.IsGenericType && typeof(T).GetGenericTypeDefinition().Equals(typeof(Nullable<>));
string itemTypeName = "";
bool typeIsValue = false;
Type itemUnderlyingType = itemType;
if (typeIsNullable)
{
// Type of enumerable item is nullable, so we need to find its base type.
itemUnderlyingType = Nullable.GetUnderlyingType(itemType);
}
typeIsValue = itemUnderlyingType.IsValueType;
itemTypeName = itemUnderlyingType.Name;
DataTable dt = new DataTable();
DataColumn col = null;
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
if ((treatItemAsValue) || (itemTypeName == "String") || (props.Length == 0))
{
// We have been asked to treat the item in the sequence as a value, or the items
// in the sequence is a string which cannot be "flattened" properly by analyzing properties.
// OR, the type has no properties to put on display, so we should just use the item directly.
// (like the base "Object" type).
typeIsValue = true;
}
if (itemTypeName == "DataRow")
{
// Special case. If our enumerable type is DataRow, then we can utilize a more appropriate
// (built-in) extension method to convert enumerable DataRows to a DataTable.
dt = ((IEnumerable<DataRow>)items).CopyToDataTable();
}
else
{
// We must have an enumerable sequence/collection of some other type, possibly anonymous.
// Get properties of the enumerable to add as columns to the data table.
if (typeIsValue)
{
// Our enumerable items are of a value type (e.g. integers in a one-dimensional array).
col = dt.Columns.Add();
// Whether or not the type is nullable, the value might be null (e.g. for type "Object").
col.AllowDBNull = true;
col.ColumnName = itemTypeName;
col.DataType = itemUnderlyingType;
// Now walk through the enumeration and add rows to our data table (single values).
foreach (var item in items)
{
dt.Rows.Add(item);
}
}
else
{
// The type should be something we can walk through the properties of in order to
// generate properly named and typed columns of our DataTable.
foreach (var prop in props)
{
Type propType = prop.PropertyType;
// Is it a nullable type? Get the underlying type.
if (propType.IsGenericType && propType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
propType = new NullableConverter(propType).UnderlyingType;
}
dt.Columns.Add(prop.Name, propType);
}
// Now walk through the enumeration and add rows to our data table.
foreach (var item in items)
{
if (item != null)
{
// Can only add an item as a row if it is not null.
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
dt.Rows.Add(values);
}
}
}
}
// Give the DataTable a reasonable name.
if (tableName.Length == 0)
{
if (typeof(T).IsAnonymous())
{
// Anonymous types have really goofy names, so there is no use using that as table name.
tableName = "Anonymous";
}
else
{
// This is NOT an anonymous type, so we can use the type name as table name.
tableName = typeof(T).Name;
}
}
return dt;
}
Hope someone finds this useful...

Related

C#: Why does my string return object type and not the value it contains?

I am looping through a List, and trying to instantiate one of the properties as a string, but it returns the type:
{Namespace.Collection}
If I put a break-point, I can see that it holds the value I need.
How can I make it return the value and not the type?
foreach (var PropertyName in ListName) {
string n = PropertyName.ToString();
}
UPDATE (added more of my code, as well as an attempt of implementing suggested solutions):
foreach (DataRow dr in ds.Tables[0].Rows) {
//PaidTrips is my ObservableCollection instance.
//PaidTrip is my holder class which it has been bound to.
PaidTrips.Add(new PaidTrip {
LicenseHolderID = dr[0].ToString(),
// adding more properties
});
List<PaidTrip> theseTrips = PaidTrips
.GroupBy(p => new { p.LicenseHolderID })
.Select(g => g.First())
.ToList();
foreach (PaidTrip PaidTrips in theseTrips) {
foreach (var LicenseHolderID in PaidTrips.GetType().GetProperties()) {
string n = LicenseHolderID.GetValue(PaidTrips).ToString();
// code to create PDF
}
gfx.DrawString(n, new XFont("Arial", 40, XFontStyle.Bold), ridelGreen, new XPoint(40, 350));
This is what I do with string n. But when the PDF is created, the string output is System.Action1[System.Action]`
What am I doing wrong?
You need to loop through the Property Types in your custom class, after looping through the list. First we need an additional loop - to loop through each ClassName Object in ListName list.
foreach (ClassName myObj in ListName)
{
foreach (var PropertyName in myObj.GetType().GetProperties())
{
string n = PropertyName.GetValue(myObj).ToString();
}
}
Then we need to loop the actual properties of the current loop ClassName object.
Then you pass the argument .GetValue (as you are now looping through the properties - the actual properties assigned, not the definition of properties).
After, you still need to specify what object you want the value of. So by passing myObj, you are specifying the ClassName->Property of the current loop of ListName.
EDIT:
List<Notes> myNotesNow = new List<Notes>();
myNotesNow.Add(new Notes
{
note1 = "Valuye"
// adding more properties
});
List<Notes> theseTrips = myNotesNow;
foreach (Notes PaidTrips in theseTrips)
{
foreach (var myVariable in PaidTrips.GetType().GetProperties())
{
string n = myVariable.GetValue(PaidTrips).ToString();
string forBreakPoint = "";
// code to create PDF
}
}
For your question - I guess that ListName is not of type string and so you get the expected behavior (see: https://learn.microsoft.com/en-us/dotnet/api/system.object.tostring?view=net-5.0#the-default-objecttostring-method)
In that case you can override the ToString() function of that object to return whatever you need like this:
public override string ToString()
{
return "whatever you want including class properties";
}
On another note, the general approach to variable naming in C# is camelCase and starts with lower case so I suggest to name your variables propertName instead of PropertyName and listName instead of ListName.
Moreover - naming variables for how they are implemented (ListName) is not a best practice as it binds them together, not allowing flexibility in case implementation changes (that comment is true only if it makes sense, as I dont see all the code)
Cheers

How do I use MemoryCache to speedup translation of various objects into strings?

I have a large dataset (IEnumerable of [Table]-attributed class objects) from a Linq-To-Sql query and I need to produce a CSV file from it. I loop over the dataset and for each item I convert the value of each property of the item into a string using various formatting options.
Type t = typeof(T);
var properties = t.GetProperties();
foreach (var item in list)
{
foreach (var property in properties)
{
// This is made using delegates, but whatever
object value = property.GetValue(item, null);
// convert to string and feed to StringBuilder
}
}
The problem is that conversion takes even longer that running the query. The dataset contains heavily denormalized data - numerous items have the same properties having the same values and only some properties having different values. Each property value is translated separately for each item in the dataset. So my code converts the same data into the same strings - over and over. And I'd like to somehow speed this up, preferable without changing the SQL query.
Looks like MemoryCache class could work, but I need to craft unique keys for each object. I can't figure out how I could craft such keys reliably and efficiently enough.
How do I make use of MemoryCache so that I can cache translation results for objects of different types?
If you just want to speed it up I would suggest ExpressionTrees more than MemoryCache. This assumes you don't have nested objects to want to read and I can use reflection on the first item and it will be the same for each item in the IEnumerable - which from your example code in the question seems correct.
Also if it's big and you are going to just write it out to a file I would suggest going straight to a FileStream instead of a StringBuilder.
public class CSV
{
public static StringBuilder ToCSV(IEnumerable list)
{
Func<object, object[]> toArray = null;
var sb = new StringBuilder();
// Need to initialize the loop and on the first one grab the properties to setup the columns
foreach (var item in list)
{
if (toArray == null)
{
toArray = ItemToArray(item.GetType());
}
sb.AppendLine(String.Join(",", toArray(item)));
}
return sb;
}
private static Func<object, object[]> ItemToArray(Type type)
{
var props = type.GetProperties().Where(p => p.CanRead);
var arrayBody = new List<Expression>();
// Create a parameter to take the item enumeration
var sourceObject = Expression.Parameter(typeof (object), "source");
// Convert it to the type that is passed in
var sourceParam = Expression.Convert(sourceObject, type);
foreach (var prop in props)
{
var propType = prop.PropertyType;
if (IsValueProperty(propType))
{
// get the value of the property
Expression currentProp = Expression.Property(sourceParam, prop);
// Need to box to an object if value type
if (propType.IsValueType)
{
currentProp = Expression.TypeAs(currentProp, typeof (object));
}
// Add to the collection of expressions so we can build the array off of this collection
arrayBody.Add(currentProp);
}
}
// Create an array based on the properties
var arrayExp = Expression.NewArrayInit(typeof (object), arrayBody);
// set a default return value of null if couldn't match
var defaultValue = Expression.NewArrayInit(typeof (object), Expression.Constant(null));
//Set up so the lambda can have a return value
var returnTarget = Expression.Label(typeof (object[]));
var returnExpress = Expression.Return(returnTarget, arrayExp, typeof (object[]));
var returnLabel = Expression.Label(returnTarget, defaultValue);
//Create the method
var code = Expression.Block(arrayExp, returnExpress, returnLabel);
return Expression.Lambda<Func<object, object[]>>(code, sourceObject).Compile();
}
private static bool IsValueProperty(Type propertyType)
{
var propType = propertyType;
if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof (Nullable<>))
{
propType = new NullableConverter(propType).UnderlyingType;
}
return propType.IsValueType || propType == typeof (string);
}
}

How to efficiently create a list of objects inside of a generic method?

So, I have an application which lies on a database. So far, the results of my queries all went into a DataTable object like this:
DataTable data = new DataTable();
data.Load(someQuery.ExecuteReader());
Now, I want to load my data into a list of a strongly typed objects. Something like this:
List<MyClass> data = someQuery.Load<MyClass>();
However, my first take on writing that method ended up running almost three times slower than DataTable.Load(IDataReader) method. Basically, I have user GetConstructor(null).Invoke(null) to create and object and I have used PropertyInfo.SetValue(reader.GetValue()) to fill it with data.
Is there a better way to do this?
The method used:
public List<T> LoadData<T>(DbCommand query)
{
Type t = typeof(T);
List<T> list = new List<T>();
using (IDataReader reader = query.ExecuteReader())
{
while (reader.Read())
{
T newObject = (T)t.GetConstructor(null).Invoke(null);
for (int ct = 0; ct < reader.FieldCount; ct++)
{
PropertyInfo prop = t.GetProperty(reader.GetName(ct));
if (prop != null)
prop.SetValue(newObject, reader.GetValue(ct), null);
}
list.Add(newObject);
}
}
return list;
}
To do this efficiently requires metaprogramming. You can use libraries to help. For example, "FastMember" includes a TypeAccessor which provides fast access to instance creation and member-access by name. However, this example is also basically exactly how "dapper" works, so you could just use dapper:
int id = ...
var data = connection.Query<Order>(
"select * from Orders where CustomerId = #id",
new { id }).ToList();
You can also open up the "dapper" code to see what it does.
You can execute your query using linQ and get the Generic List and then if you want to conver it to DataTable then use the following Code, it may help you.
public DataTable ListToDataTable<T>(IEnumerable<T> list)
{
PropertyDescriptorCollection properties =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in list)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
it will work for any strongly type class. Please check the time it takes to execute.
Thanks,

Casting Linq Query to DataSet fails

I'm migrating an older .net application to .net 4, this migration has to be done in several stages, thats why some of the methods might seem a bit unconventional. Anyway...
What I have is a Stored Procedure (Analysis_select) returning one row with several columns with the result. If i call it with
var result = dbContext.Analysis_select(user.UserId, Year, Week);
everything is fine, i can view the data in with the debugger or display it in a grid view or something like that, so the expression and Stored Procedure really works! But the result is not compatible with the rest of the code so...
If I try to cast it to DataSet it fails, Visual Studio actually sais this is ok but when rendering on a web page it crashes
var result = (DataSet)dbContext.Analysis_select(user.UserId, Year, Week);
The error is as follows
Unable to cast object of type 'SingleResult`1[Analysis_select]' to type 'System.Data.DataSet'.
I've read about some other conversions from linq to DataSet but most of the methods seems a bit excessive for this. The reason why I want to keep the DataSet is that there's tens of thousands of lines of code depending on such results. Sucks yes, but can you help me fix this?
Any help is highly appreciated, thanks!
I'm not suggesting this as a great solution or best practices; there is most definitely a different (and probably better) way.
For a case where you have IEnumerable and no other means to create a data table, reflection can step in.
You could use something like below...
public static class ExtensionMethods
{
public static DataTable ToDataTable<T>(this IEnumerable<T> items)
{
DataTable table = new DataTable();
var properties = typeof(T).GetProperties();
foreach (var propertyInfo in properties)
{
table.Columns.Add(propertyInfo.Name, typeof(object));
}
foreach (var item in items)
{
var row = properties.Select(p => NormalizeObject(p.GetValue(item, null))).ToArray();
table.Rows.Add(row);
}
return table;
}
private static object NormalizeObject(object value)
{
Binary bin = value as Binary;
if (bin != null)
{
return bin.ToArray();
}
XElement element = value as XElement;
if (element != null)
{
return element.ToString();
}
return value;
}
}
You will need to write an extension method to convert the IEnumerable into a DataSet. Here is an example of how to convert IEnumerable to a DataTable.
private DataTable ToDataTable<T>(List<T> items)
{
var table = new DataTable(typeof (T).Name);
PropertyInfo[] props = typeof (T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in props)
{
Type t = GetCoreType(prop.PropertyType);
table.Columns.Add(prop.Name, t);
}
foreach (T item in items)
{
var values = new object[props.Length];
for (int i = 0; i < props.Length; i++)
{
values[i] = props[i].GetValue(item, null);
}
table.Rows.Add(values);
}
return table;
}
public static Type GetCoreType(Type t)
{
if (t != null && IsNullable(t))
{
if (!t.IsValueType)
{
return t;
}
else
{
return Nullable.GetUnderlyingType(t);
}
}
else
{
return t;
}
}
public static bool IsNullable(Type t)
{
return !t.IsValueType || (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
Here's a link to the source of this solution: http://www.chinhdo.com/20090402/convert-list-to-datatable/
did you check this tutorial http://msdn.microsoft.com/en-us/library/bb386921.aspx from MS? Otherwise there is no direct conversion between LINQ result and Dataset.
With LINQ2SQL stored procedures you never get DataSets. What you get is Exactly that, a SingleResult. Is an IEnumerable.

Problem in converting a generic list<string> or string[] array to datatable

I have the below function
public static DataTable ToTable<T>(this IEnumerable<T> listItem)
{
//Return null if the list is empty
if (listItem == null || listItem.Count() == 0) return null;
//Gets the type of the object
var listType = listItem.First().GetType();
//Initialize a new datatable
var dataTable = new DataTable(listType.Name);
//Create the datatable column names and types
listType.GetProperties().ToList().ForEach(col => dataTable.Columns.Add(col.Name, col.PropertyType));
//Get the datatable column names
var dataTableColumnNames = dataTable.GetDatatableColumnNames();
listItem.ToList().ForEach(item =>
{
//create a new datarow
var dataRow = dataTable.NewRow();
dataTableColumnNames
.Where(propName => listType.GetProperty(propName) != null)
.ToList()
.ForEach(columnName =>
//Exception happens here in the next line
dataRow[columnName] = listType.GetProperty(columnName).GetValue(item, null));
//Add the row to the data table
dataTable.Rows.Add(dataRow);
});
//Commit the changes to the datatable
dataTable.AcceptChanges();
return dataTable;
}
It works great for dictionary object and generic list as List<MyClass> .. but not for
List<string> or string[].
For those I am getting an exception as Parameter count mismatch.
The error is coming at
dataRow[columnName] = listType.GetProperty(columnName).GetValue(item, null));
What is the mistake that is happening?
Please help
Here's the deal. The index operator is actually considered a property when using reflection, hence parameter count mismatch.
If you break into your code and check the properties that are actually being enumerated by GetProperties(), you'll see the "Chars" property. That's the String's index operator. Since you didn't provide an index, you're getting a Parameter Count Mismatch error.
In essence, I assume string doesn't have any properties you want to put in your data table, but rather the string instance IS what you want to put in the data table.
You could create a model to store the string in, with the string as a property on the model, then the string would be stored with your current code. Otherwise, you will need to rethink your table generation algorithm for primitive types.
I hope this helps :)
Because one of the public properties of string is an indexer and you pass null as the index value. So you effectively end up doing this: string[null] which ends up in an exception.
I haven't verified this as I don't have VS available right now so I might be wrong but I'm pretty sure that's the problem.
Update: This question answers how you detect an indexed property: C# Reflection Indexed Properties

Categories

Resources