Fill generic list from SqlDataReader - c#

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

Related

Compare two struct List and find if item of list1 appears in item of list2 in C#

I have two record structures and two lists as follows:
public struct gtAliasRecType : ICloneable
{
public int lRecordNum;
public double dLocationCd;
}
public struct gtCVARecType : ICloneable
{
public double dLocationCd;
}
static public List<gtCVARecType> LCVARec = null;
static public List<gtAliasRecType> LAliasRec = null;
Now i want to iterate "LAliasRec" list and find whether similar "dLocationCd" exists in "LCVARec" list or not.
I tried using "Contains" and "Find" function of list1 but ended up in errors.
public static void XYZ()
{
gtAliasRecType uAliasRec = gtAliasRecType.CreateInstance();
gtCVARecType uCVARec = gtCVARecType.CreateInstance();
for (int i = 0; i < LAliasRec.Count; i++)
{
uAliasRec = LAliasRec[i];
//trying Find method
gtCVARecType c1 = LCVARec.Find(uAliasRec.dLocationCd);
//trying Contains method
bool nReturn = LCVARec.Contains( uAliasRec.dLocationCd );
}
}
However, i ran into "Cannot convert from 'double' to 'gtCVARecType' error.
Contains & Find
Thanks in advance :)
You can't use Contains to find an item of a different type. You can use Find, but I'd personally use the LINQ Any method:
foreach (var uAliasRec in LAliasRec)
{
bool nReturn = LCVARec.Any(rec => rec.dLocationCd == uAliasRec.dLocationCd);
// Presumably do something with nReturn
}
If the lists are large, you might want to create a HashSet<double> for all the locations first, which is an up-front cost that will make everything else cheaper:
HashSet<double> locations = new HashSet<double>(LCVARec.Select(rec => rec.dLocationCd));
foreach (var uAliasRec in LAliasRec)
{
bool nReturn = locations.Contains(uAliasRec.dLocationCd);
// Presumably do something with nReturn
}
As an aside, I'd strongly advise you to start following regular .NET naming conventions. In its current form, your code is going to be very hard for anyone used to regular C# code to work with.
What about using Intersect
var results = LAliasRec
.Select(x => x.dLocationCd)
.Intersect(LCVARec.Select(x => x.dLocationCd));
bool exists = results.Count() > 0;
Select only the double values, and get intersected ones. If Count greater than 0, you got mutual property values.
You can use LINQ and Inner join to find the intersection of two lists.
var query = from lcva in LCVARec
join lAlias in LAliasRec on lcva.dLocationCd equals lAlias.dLocationCd
select lcva;
Console.WriteLine(query.Count()); //prints number of matching items.
Update
If you can change the List<T> to SortedList<TKey, TValue> of SortedDictionary<TKey, TValue> it will help in quicker lookup.
If you prefer to use Contains() you must implement IEquatable<T> and if you want performance you have to Sort() which needs the class to have IComparable<T> and then do BinarySearch
Reference : https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=netcore-3.1#remarks

How to find specific object from list in C#?

How is it possible to find a specific object from a list?
Lets say i have a function that takes an object and a list that contains objects of this type and returns the number at which position the specific object is found.
The only way i could think of a solution is to run the list through with a foreach loop, but isn't there a better way?
Thanks
You can use the IndexOf(T item) method:
myList.IndexOf(myItem);
It returns the index of the first occurrence of the item.
The only way i could think of a solution is to run the list through with a foreach loop
Generally, you need a loop (a for or foreach) to find an object in a list. You could use it directly, or through a function that iterates over list elements, but there is going to be a loop. There is no way around it: unless you know something special about the way the elements of the array are arranged, you have to look at them all.
One case of knowing something special about arrangement of elements is knowing that an array is sorted. If this is the case, and when you know the value of the attribute on which the element is sorted, you can find the element much faster by using binary search.
You could use linq expressions
List.where(condition)
Or
List.Select(condition).FirstOrDefault
Based on search condition it will return the item you want.
You can use method IndexOf or if you use a special condition then you can use method
public int FindIndex(Predicate<T> match);
where match is delegate
public delegate bool Predicate<T>(T obj);
In fact it is similar to standard C++ algorithm std::find_if
To see whether object is there You might just need List<T>.Contains method
It states,
Determines whether an element is in the List.
And you need to use it like List<T>.Contains(T type item) , where T is the same type of List and item you need to compare. In your case it's a the type of Object
And to return the index you can use List<T>.IndexOf Method
Searches for the specified object and returns the zero-based index of the first occurrence within the entire List.
Simple Console program
class Program
{
static void Main(string[] args)
{
MyType a = new MyType() { id = 10 };
MyType b = new MyType() { id = 20 };
MyType c = new MyType() { id = 30 };
List<MyType> testList = new List<MyType>();
testList.Add(a);
testList.Add(b);
Console.WriteLine(testList.Contains(a)); // <= Will return true
Console.WriteLine(testList.Contains(c)); // <= Will return false
Console.WriteLine(testList.IndexOf(a)); // <= will return 0 : the index of object a
Console.ReadKey();
}
}
// A simple class
class MyType
{
private int ID;
public int id
{
get { return ID; }
set { ID = value; }
}
}

Return List With Multiple Elements

I'm struggling getting my head around returning a List that has multiple elements (PHP background - I'd use arrays for this in PHP).
I have a large string that I'm parsing in a WHILE loop. I want to return a List with pairs of elements. I've tried something like this:
static public List<string> getdata(string bigfile)
{
var data = new List<string>[] { new List<string>(), new List<string>() }; // create list to hold data pairs
While (some stuff)
{
// add element pair to List<data>
data[0].Add(this); // add element to list - 'this' is declared and assigned (not shown)
data[1].Add(that); // add element to list - 'that' is declared and assigned (not shown)
}
return data???; // <<-- This is where I'm failing. I can, of course, return just one of the elements, like return data[0];, but I can't seem to get both elements (data[0] and data[1]) together.
} // end getdata
I've reviewed some answers, but I'm missing something. I've tried several things syntactically for the return value, but no luck. Any help would be greatly appreciated. I hate asking questions, but I've spent some time on this and I'm just not finding what I want.
Change method declaration to:
static public List<string>[] getdata(string bigfile)
try with
static public List<string>[] getdata(string bigfile)
{
....
}
Or
But if you need to return list of string array, then change the method as
static public List<string[]> getdata(string bigfile)
{
List<string[]> data= new List<string[]>();
While (some stuff)
{
data.Add(this);
data.Add(that);
}
return data;
}
The problem there is you are returning a Collection of List so the return type is mismatching. Try this one,
var data = new List<string>();
while (some stuff)
{
data.Add("test0");
data.Add("test1");
}
return data;
I want to return a List with pairs of elements
If you want pairs, use pairs:
static public List<Tuple<string, string>> getdata(string bigfile)
{
var data = new List<Tuple<string, string>>(); // create list to hold data pairs
while (some stuff)
{
// add element pair
data.Add(Tuple.Create(a, b)); // 'a' is declared and assigned (not shown)
// 'b' is declared and assigned (not shown)
}
return data;
}

How to instantiate a generic list without knowing the type

I created a method to organize a generic list without know the type, it will sort if its int or decimal.
However the code that retrieves the values from textboxes uses List
I tried to convert it to List, but it doesnt work.
I want this code to work if they type integers or decimals or strings in the textboxes.
This was part of an interview question where they asked not to use the sort method, and that the input should receive for example INTS or DECIMALS
private void btnSort_Click(object sender, EventArgs e)
{
List<int> list = new List<int>();
list.Add(int.Parse(i1.Text));
list.Add(int.Parse(i2.Text));
list.Add(int.Parse(i3.Text));
list.Add(int.Parse(i4.Text));
list.Add(int.Parse(i5.Text));
Sort(list);
StringBuilder sb = new StringBuilder();
foreach (int t in list)
{
sb.Append(t.ToString());
sb.AppendLine();
}
result.Text = sb.ToString();
}
private void Sort<T>(List<T> list)
{
bool madeChanges;
int itemCount = list.Count;
do
{
madeChanges = false;
itemCount--;
for (int i = 0; i < itemCount; i++)
{
int result = Comparer<T>.Default.Compare(list[i], list[i + 1]);
if (result > 0)
{
Swap(list, i, i + 1);
madeChanges = true;
}
}
} while (madeChanges);
}
public List<T> Swap<T>(List<T> list,
int firstIndex,
int secondIndex)
{
T temp = list[firstIndex];
list[firstIndex] = list[secondIndex];
list[secondIndex] = temp;
return list;
}
I wanted that something like this: but gives error
Error 1 The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?) c:\users\luis.simbios\documents\visual studio 2010\Projects\InterViewPreparation1\InterViewPreparation1\Generics\GenericsSorting1.cs 22 18 InterViewPreparation1
List list = new List();
list.Add(i1.Text);
list.Add(i2.Text);
Sort(list);
because its an interview question in which they asked not to use the
sort method.
In this case you can add a generic constraint IComparable<T> and then use the CompareTo() method:
private void Sort<T>(List<T> list) where T : IComparable<T>
{
//...
}
Edit:
You would have to write custom code to determine whether the input is string, int or decimal, i.e. use TryParse(..) - this will be very fragile though. Once you do know the type (one way or another) you can use MakeGenericType() and Activator.CreateInstance() to create your List<T> object at run time and then use MakeGenericMethod() to call your generic method:
Type type = typeof(string);
IList list = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
//add items to list here
var p = new Program();
MethodInfo method = typeof(Program).GetMethod("Sort");
MethodInfo genericMethod = method.MakeGenericMethod(new Type[] { type });
genericMethod.Invoke(p, new [] {list} );
I am pretty sure that is not what the interview question intended to ask for.
First, as Jason points out, let the platform do the work for you - call .Sort.
Second, it looks to me like you're going to have to select the 'T' of the List based on examining the contents of the textboxes so you can handle ints vs. strings, etc. And then assign items to the list based on that. But once you have decided, your sort won't care.
You're not going about this the right way. Embrace generics correctly. What you want is this:
public string Foo<T>(IEnumerable<string> strings) where T : struct, IComparable<T> {
var list = strings.Select(s => (T)Convert.ChangeType(s, typeof(T))).ToList();
list.Sort((x, y) => (x.CompareTo(y)));
return String.Join("\n", list);
}
Now you can say
string response = Foo<int>(strings);
or
string response = Foo<decimal>(strings);
depending on which you want.
Note that
We use List<T>.Sort to do the sorting.
We use String.Join to build the string to display back to the user.
This should compile, but please excuse trivial errors if it doesn't. I can't fire up the ol' compiler right now.
Edit: I see you edited in that you can't use List<T>.Sort. It's easy enough to replace my use of List<T>.Sort with your own implementation.
Try something like:
private static IList foobar(Type t)
{
var listType = typeof(List<>);
var constructedListType = listType.MakeGenericType(t);
var instance = Activator.CreateInstance(constructedListType);
return (IList)instance;
}
Then use:
IList list = foobar(TYPE);
Where TYPE is the type that you want you list to be.
Hope this helps!

How can I make this extension method more generic?

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

Categories

Resources