How can I make this extension method more generic? - c#

I'm having a brain fart trying to make the following method more generic such that any List<T> can be passed in for the columnValues parameter. Here's what I have:
public static DataRow NewRow(this DataTable dataTable, List<string> columnValues)
{
DataRow returnValue = dataTable.NewRow();
while (columnValues.Count > returnValue.Table.Columns.Count)
{
returnValue.Table.Columns.Add();
}
returnValue.ItemArray = columnValues.ToArray();
return returnValue;
}
I could change it to a List<object> and convert the original list prior to passing it to the method but I'm sure there is a better option :-)
Edit:
Frank's post made me rethink this. In most cases that source List<T> would be a List<object> since the column values will most likely be different types.
For my initial use a List<string> made sense because I was creating a dataset from a CSV parse which is all text at that point.

Why not just use params object[]:
public static DataRow NewRow(this DataTable dataTable, params object[] objects)
{
DataRow returnValue = dataTable.NewRow();
while (objects.Length > returnValue.Table.Columns.Count)
{
returnValue.Table.Columns.Add();
}
returnValue.ItemArray = objects;
return returnValue;
}
Then you can just call it like this:
myDataTable.NewRow(1,2,"hello");

You're basically out of luck, because the Item Array of the DataRow is an array of objects, that is, ultimately you can only pass in list of objects.
If you put in a generic parameter of the list all items of the list would have to be of that type, which is highly unlikely to be useful.
Having said that, in order to get numerous columns, all with different types, you could change your extension method to accept an object into which you instantiate an anonymous type:
table.NewRow(new { A = "Hello", B = 1, C = DateTime.Now })
With the aid to convert the anonymous type values to a string,object dictionary either by reflection or by a dynamic method it should be a fairly useful thing.

What about
IEnumerable<object>
in connection with
columnValues.Select(x => x.ToString()).ToArray();

What about using a closure to specify how to generate the ItemArray based upon your input type
public static DataRow NewRow<T>(this DataTable dataTable, List<T> columnValues, Func<T, string> itemArrayCriteria)
{
DataRow returnValue = dataTable.NewRow();
while (columnValues.Count > returnValue.Table.Columns.Count)
{
returnValue.Table.Columns.Add();
}
returnValue.ItemArray = columnValues.Select(x => itemArrayCriteria(x)).ToArray();
return returnValue;
}

Related

Cast List<object> to List<string>

I've spent way to much time on trying to resolve this and I don't understand why I can't cast this. I'm executing a query and pulling all the values from a specific column that is then stored in a List since it could be an int, string, or bool. The issue is during casting, I would like a dynamic solution that can validate the object type and cast accordingly.
//This handles the db connection and makes calls DbItems class
public List<object> GetDBColumns(string sqlQuery, string column)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
var reader = new SqlCommand(sqlQuery, connection).ExecuteReader();
var values = DbItems.GetColumns(reader, column);
connection.Close();
return values;
}
}
Public class DbItems
{
DbItems(SqlDataReader reader, string column)
{ //GetInt32 won't be able to handle other types of course, what could I use?
columnData.Add(reader.GetInt32(reader.GetOrdinal(column)));
}
List<object> columnData = new List<object>();
//I'm calling this static method that invokes the constructor
public static List<object> GetColumns(SqlDataReader reader, string column)
{
List<object> dataSet = new List<object>();
while (reader.Read())
{
dataSet.Add(new DbItem(reader, column).columnData);
}
return dataSet;
}
}
This works without issue, but then I'll get an int value that I'd like to cast a string and I've used Cast<>, (string)columnData[0], and a couple other suggestions online and nothing works. Please assist.
Casting List<object> to List<string> would require contravariance of List's generic parameter T. Unfortunatelly List<T> does not meet criteria for contravariance, because it is not an interface, delegate or array type, and because making it's generic parameter contravariant wouldn't be type-safe (doing so would e.g. allowed List<string> to contain not only strings, but any other object, which is obviously nonsence, not type-safe and therefore not allowed). Fot this reason, you cannot cast List<object> to List<string>.
What you can, however, is to create new List<string> and copy there all items from original List<object>, while converting each item to string. Item conversion with Cast<> or (string)columnData[0] will not work here (unless items are actually a strings), because casting a reference-type object to another reference-type only performs assignment compatibility check, but does not perform any conversion of the object.
Luckily, converting to string is trivial, since all objects inherits .ToString() method from Object type. So you can convert to List<string> with the following:
List<string> stringList = columnData.ConvertAll(item => item?.ToString());
But of course, you can use any other conversion, if .ToString() does not meet your needs, such as using Convert class to perform conversion between primitive types.
The easiest way to do so would be just call the ToString Method, since all object have a ToString Method
var values = columnData.Select(x => x?.ToString()).ToList();

Fill generic list from SqlDataReader

I'm reading some fields with different types from an SqlDataReader based on the column ID. I tried to write a generic function to get the fields in a List.
private static List<T> GetSqlFields<T>(SqlDataReader reader, int columnId)
{
var fields = new List<T>();
while (reader.Read())
{
T f = reader.Get... // Problem is here
fields.Add(f);
}
return fields;
}
Problem is I only see methods GetString, GetInt32, etc.. Is there a way to get the fields based on T? Thank you.
I'm doing this to be able to write:
int fooColumnId = reader.GetOrdinal("Foo");
int barColumnId = reader.GetOrdinal("Baar");
List<string> foos = GetSqlFields<string>(reader, fooColumnId);
List<int> baars = GetSqlFields<int>(reader, barColumnId);
You'll want: SqlDataReader.GetFieldValue<T>(int i).
This method (as the linked docs shows) takes an int for the column number and returns an object of type T. Of note is that this doesn't support any T but the list that it supports (on that page) covers the things that you might expect to be covered (ie looks at a glance like all types which are .NET equivalents of SQL data types).
If you didn't have the index of the column you are interested in (it looks like you do in this case but I'm adding for completeness) then you can use GetOrdinal(String) to get the column number from a column name.
Don't know what you're after, but:
private static List<T> GetSqlFields<T>(SqlDataReader reader, int columnId,
Func<IDataReader, int, T> accessor)
{
var fields = new List<T>();
while (reader.Read())
{
T f = accessor(reader, columnId);
fields.Add(f);
}
return fields;
}
List<int> baars = GetSqlFields<int>(reader, barColumnId, (dr, i) => dr.GetInt32(i));

LINQ object of IEnumerable type does not return a DataTable

Could someone briefly me explain why the I cannot use the CopyToDataTable method on the following linq object (of IEnumurable type)?
var query = from r in SourceData.AsEnumerable()
group r by r["fruitid"] into Dt
select new
{
Group = Dt.Key,
Sum = Dt.Sum((t)=> double.Parse(t["name"].ToString()))
};
Reminder: My aim consists of retrieving the resulting DataTable following the GroupBy Clause
There is a restriction on the generic type of CopyToDataTable.
Take a look at the declaration of the method:
public static DataTable CopyToDataTable<T>(this IEnumerable<T> source)
where T : DataRow
{
}
If you notice that second line, your IEnumerable<T> must be an enumerable of DataRows. What you have is an enumerable of anonymous objects.
Depending on what your DataTable is coming from, bound to, etc, there are many other ways to accomplish what you're trying to do, but this is why you don't have that method available.

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

how to add an associative index to an array. c#

i have an array of custom objects. i'd like to be able to reference this array by a particular data member, for instance myArrary["Item1"]
"Item1" is actually the value stored in the Name property of this custom type and I can write a predicate to mark the appropriate array item. However I am unclear as to how to let the array know i'd like to use this predicate to find the array item.
I'd like to just use a dictionary or hashtable or NameValuePair for this array, and get around this whole problem but it's generated and it must remain as CustomObj[]. i'm also trying to avoid loading a dictionary from this array as it's going to happen many times and there could be many objects in it.
For clarification
myArray[5] = new CustomObj() // easy!
myArray["ItemName"] = new CustomObj(); // how to do this?
Can the above be done? I'm really just looking for something similar to how DataRow.Columns["MyColumnName"] works
Thanks for the advice.
What you really want is an OrderedDictionary. The version that .NET provides in System.Collections.Specialized is not generic - however there is a generic version on CodeProject that you could use. Internally, this is really just a hashtable married to a list ... but it is exposed in a uniform manner.
If you really want to avoid using a dictionary - you're going to have to live with O(n) lookup performance for an item by key. In that case, stick with an array or list and just use the LINQ Where() method to lookup a value. You can use either First() or Single() depending on whether duplicate entries are expected.
var myArrayOfCustom = ...
var item = myArrayOfCustom.Where( x => x.Name = "yourSearchValue" ).First();
It's easy enough to wrap this functionality into a class so that external consumers are not burdened by this knowledge, and can use simple indexers to access the data. You could then add features like memoization if you expect the same values are going to be accessed frequently. In this way you could amortize the cost of building the underlying lookup dictionary over multiple accesses.
If you do not want to use "Dictionary", then you should create class "myArrary" with data mass storage functionality and add indexers of type "int" for index access and of type "string" for associative access.
public CustomObj this [string index]
{
get
{
return data[searchIdxByName(index)];
}
set
{
data[searchIdxByName(index)] = value;
}
}
First link in google for indexers is: http://www.csharphelp.com/2006/04/c-indexers/
you could use a dictionary for this, although it might not be the best solution in the world this is the first i came up with.
Dictionary<string, int> d = new Dictionary<string, int>();
d.Add("cat", 2);
d.Add("dog", 1);
d.Add("llama", 0);
d.Add("iguana", -1);
the ints could be objects, what you like :)
http://dotnetperls.com/dictionary-keys
Perhaps OrderedDictionary is what you're looking for.
you can use HashTable ;
System.Collections.Hashtable o_Hash_Table = new Hashtable();
o_Hash_Table.Add("Key", "Value");
There is a class in the System.Collections namespace called Dictionary<K,V> that you should use.
var d = new Dictionary<string, MyObj>();
MyObj o = d["a string variable"];
Another way would be to code two methods/a property:
public MyObj this[string index]
{
get
{
foreach (var o in My_Enumerable)
{
if (o.Name == index)
{
return o;
}
}
}
set
{
foreach (var o in My_Enumerable)
{
if (o.Name == index)
{
var i = My_Enumerable.IndexOf(0);
My_Enumerable.Remove(0);
My_Enumerable.Add(value);
}
}
}
}
I hope it helps!
It depends on the collection, some collections allow accessing by name and some don't. Accessing with strings is only meaningful when the collection has data stored, the column collection identifies columns by their name, thus allowing you to select a column by its name. In a normal array this would not work because items are only identified by their index number.
My best recommendation, if you can't change it to use a dictionary, is to either use a Linq expression:
var item1 = myArray.Where(x => x.Name == "Item1").FirstOrDefault();
or, make an extension method that uses a linq expression:
public static class CustomObjExtensions
{
public static CustomObj Get(this CustomObj[] Array, string Name)
{
Array.Where(x => x.Name == Name).FirstOrDefault();
}
}
then in your app:
var item2 = myArray.Get("Item2");
Note however that performance wouldn't be as good as using a dictionary, since behind the scenes .NET will just loop through the list until it finds a match, so if your list isn't going to change frequently, then you could just make a Dictionary instead.
I have two ideas:
1) I'm not sure you're aware but you can copy dictionary objects to an array like so:
Dictionary dict = new Dictionary();
dict.Add("tesT",40);
int[] myints = new int[dict.Count];
dict.Values.CopyTo(myints, 0);
This might allow you to use a Dictionary for everything while still keeping the output as an array.
2) You could also actually create a DataTable programmatically if that's the exact functionality you want:
DataTable dt = new DataTable();
DataColumn dc1 = new DataColumn("ID", typeof(int));
DataColumn dc2 = new DataColumn("Name", typeof(string));
dt.Columns.Add(dc1);
dt.Columns.Add(dc2);
DataRow row = dt.NewRow();
row["ID"] = 100;
row["Name"] = "Test";
dt.Rows.Add(row);
You could also create this outside of the method so you don't have to make the table over again every time.

Categories

Resources