Generics and inheritance - c#

Two problems in one here ...
I have a set of DataRow wrappers (in VS2008) that inherit from a base class (called RecordBase). They all have a field called TableName. I wanted to make a generic enumerator that is an extension method to a DataSet. The specific TableName would select which table in the DataSet to enumerate. I'd like to write
public static IEnumerable<T> GetRecords<T>(this DataSet MySet) where T : RecordBase
{
foreach (DataRow row in MySet.Tables[T.TableName].Rows)
{
yield return new T(row);
}
}
Problem 1: I can’t find a way to have an overrideable static field, forcing me to create a dummy instance of the wrapper just to get the TableName.
Problem 2: Less serious, even though the wrappers (and the base) have a constructor that accepts a DataRow the compiler still insists that I use the parameterless constructor constraint.
All of which leaves me with code looking like
public static IEnumerable<T> GetRecords<T>(this DataSet MySet) where T : RecordBase, new()
{
string TableName = (new T()).TableName;
foreach (DataRow row in MySet.Tables[TableName].Rows)
{
T record = new T();
record.RowData = row;
yield return record;
}
}
Any ideas?

You can use an custom attribute for the table name and Activator to instantiate the type:
[Table("Customers")]
class Customer : RecordBase { }
//...
public static IEnumerable<T> GetRecords<T>(this DataSet MySet) where T : RecordBase
{
var attribT = typeof(TableAttribute);
var attrib = (TableAttribute) typeof(T).GetCustomAttributes(attribT,false)[0];
foreach (DataRow row in MySet.Tables[attrib.TableName].Rows)
{
yield return (T) Activator.CreateInstance(typeof(T),new[]{row});
}
}

Related

loading a List<T> from datatable with generic method

I load 10 different List< T > with datatable.
The datatable is loaded from a Sqlite Database.
To do this, I'm repeating 10 times the same method.
Here is an example of my code:
//List to be load
public static List<AircraftModel> Aircraft = new List<AircraftModel>();
public static List<AirlineModel> Airline = new List<AirlineModel>();
//Method to load the list Aircraft with the datatable
public void LoadAircraft(DataTable data)
{
foreach (DataRow row in data.Rows)
{
Aircraft.Add(new AircraftModel
{
Id = Int32.Parse(row["id"].ToString()),
Registration = row["registration"].ToString(),
Capacity = Int32.Parse(row["capacity"].ToString()),
Type = row["type"].ToString()
});
}
}
//Method to load the List Airline with datatable
public void LoadAirline(DataTable data)
{
foreach (DataRow row in data.Rows)
{
Airline.Add(new AirlineModel
{
Code = row["code"].ToString(),
AirlineName = row["name"].ToString()
});
}
}
Would it be possible to optimize my code with a generic method like this:
//call method to load the List
GetData<AircraftModel>(Aircraft, datatable);
GetData<AirlineModel>(Airline, datatable);
//Unique generic method to load the 10 List < T >
public void GetData<T>(List< T > ListToBeLoad, DataTable table)
where T : class, new()
{
foreach (DataRow row in table.Rows)
{
ListToBeLoad.Add( new T
{
......
HELP NEEDED PLEASE
......
}
}
}
thanks in advance for your reply and propositions
Cyrille
You have couple of methods, you can create a mapping method and pass it as an argument, or you can use AutoMapper.
Here is 2 examples of the first solution (Without AutoMapper)
Example of populating to existing list
PopulateList(list, dataTable, (row) =>
new AircraftModel
{
Id = int.Parse(row["id"].ToString()),
Registration = row["registration"].ToString(),
Capacity = int.Parse(row["capacity"].ToString()),
Type = row["type"].ToString()
}
);
public static void PopulateList<T>(List<T> list, DataTable data, Func<DataRow, T> mapFunc)
where T : new()
{
foreach (DataRow row in data.Rows)
{
list.Add(mapFunc(row));
}
}
Example of creating a new list and mapping the rows.
var list2 = Map(dataTable, (row) =>
new AircraftModel
{
Id = int.Parse(row["id"].ToString()),
Registration = row["registration"].ToString(),
Capacity = int.Parse(row["capacity"].ToString()),
Type = row["type"].ToString()
}
);
public static IEnumerable<T> Map<T>(DataTable data, Func<DataRow, T> mapFunc)
where T : new()
{
foreach (DataRow row in data.Rows)
{
yield return mapFunc(row);
}
}
As you may have noticed, the issue is that you need to know the columns in order to get the right data, and to know the properties in order to store it in the right place. As a result, your code needs to be aware of each of the individual classes - and that's fine: You're not going to get rid of this code.
Generally, when you want to construct something, you may want to think of a factory, that is, a method (or class) that is capable of constructing an instance of a specific type. A factory class, in your case, could look like the following:
public abstract class Factory<T>
{
public T Create(DataRow row);
}
Now the next thing you would do is creating specific instances for each type you want to create, e.g.
public sealed class AirlineModelFactory : Factory<AirlineModel>
{
public override AirlineModel Create(DataRow row)
{
return new AirlineModel
{
Code = row["code"].ToString(),
AirlineName = row["name"].ToString()
};
}
}
Now the boilerplate code is this:
public void GetData<T>(List<T> list, DataTable table, Factory<T> factory)
where T : class, new()
{
foreach (DataRow row in table.Rows)
{
list.Add(factory.Create(row));
}
}
and you may call it as
GetData(AirlineList, table, new AirlineModelFactory());
GetData(AirpoirtList, table, new AirportModelFactory());
// etc.
although, arguably, you may want to keep your factory instances around (or even inject them for inversion of control) to avoid the new.
However, as you will notice, you still need to create N classes / methods for N types.
To automate things a bit more, you could introduce a "generic" base class to your factories, e.g.
public abstract class GenericFactory
{
public abstract object CreateObject(DataRow row);
}
and then implement
public abstract class Factory<T> : GenericFactory
{
public override object CreateObject(DataRow row) => Create(row);
// ...
}
With this, you could think of a lookup dictionary Dictionary<Type, GenericFactory>. By picking the right type and then casting to the right T, you get your correct instance. This is, however, a service locator pattern - a code smell, because a missing registration in the dictionary leads to an error at runtime - but depending on your needs, it could still be of help.

How do I create a method that converts a DataRowCollection to an array of objects of a generic type in C#?

I am trying to create a method that takes a DataTable or a DataRowCollection and converts it to an array of a generic type. Something like this:
public static T[] ConvertToArray<T>(DataTable dataTable)
{
List<T> result = new List<T>();
foreach (DataRow dataRow in dataTable.Rows)
result.Add((T)dataRow);
return result.ToArray();
}
The problem is this line
result.Add((T)dataRow);
which gives Cannot convert System.Data.DataRow to T.
If I do the same thing without using a generic type, and make sure the class of the objects have a defined custom conversion operator, the code works fine.
So the question is now, how do I pull this of using generics?
You could use an object that provides the conversion on a DataRow to your type :
public interface IDataRowConverter<T>
{
T Convert(DataRow row);
}
Provide your custom converter to your function :
public static T[] ConvertToArray<T>(DataTable dataTable, IDataRowConverter<T> converter)
{
List<T> result = new List<T>();
foreach (DataRow dataRow in dataTable.Rows)
result.Add(converter.Convert(dataRow));
return result.ToArray();
}
Then, implement the interface for your needed type :
public class MyObjectDataRowConverter : IDataRowConverter<MyObject>
{
public MyObject Convert(DataRow row)
{
MyObject myObject = new MyObject();
// Initialize object using the row instance
return myObject;
}
}
You can then call your function using this code :
MyObject[] objectArray =
ConvertToArray<MyObject>(datatable, new MyObjectDataRowConverter());
Couple options I found:
result = dataTable.Select()
or
var result = new System.Data.DataRow[dataTable.Rows.Count];
dataTable.Rows.CopyTo(result, 0);

DataTable to List<T> conversion

Is there any better way than the following?
Particularly, I want to replace Activator with something else.
public static List<T> ToList<T>(DataTable dt)
{
Type type = typeof(T);
List<T> list = new List<T>();
foreach (DataRow dr in dt.Rows)
{
object[] args = new object[1];
args[0] = dr;
list.Add((T)Activator.CreateInstance(type, args));
}
return list;
}
The first thing I want to mention is that you probably don't need a list. Odds are, an IEnumerable is enough. Even if you do need a list, it's trivial to convert an IEnumerable to a list.
With that in mind, this code is a nice generic way to accomplish it:
public static IEnumerable<T> ToEnumerable<T>(DataTable dt, Func<DataRow, T> translator)
{
foreach(DataRow dr in dt.Rows)
{
yield return translator(dr);
}
}
Hopefully you can see how re-usable this is. All you need to do is supply a function that knows how to convert an individual DataRow into your T type. That function might use Activator, but it doesn't have to. It might just use a normal constructor and set a few properties.
I don't really see any way to improve this code - why do you want to avoid Activator?
One option you could explore would be to create some sort of interface like this:
interface IFoo
{
void Initialize(DataRow dr);
}
And then implement this interface on any type that gets passed to this method. Then you would constrain your generic type parameter like this:
public static List<T> ToList<T>(DataTable dt)
where T : IFoo, new()
Then change the implementation of your method like this:
public static List<T> ToList<T>(DataTable dt)
where T : IFoo, new()
{
List<T> list = new List<T>();
foreach (DataRow dr in dt.Rows)
{
T t = new T();
t.Initialize(dr);
list.Add(t);
}
return list;
}
One thing I'd add to Andrew's answer is if you go that route you can (sorta) avoid the Activator class by constraining the generic method with a new() constraint.
public static List<T> ToList<T>(DataTable dt)
where T : IFoo, new()
{
...
foreach ( ... ) {
var foo = new T();
foo.Initialize(dataRow);
list.Add(foo);
}
...
}
The reason I say "sorta" is because C# actually just compiles that into an Activator.CreateInstance call at compile-time anyway. But it looks much cleaner.

C# Generics question

I am a bit rusty on generics, trying to do the following, but the compiler complains:
protected List<T> PopulateCollection(DataTable dt) where T: BusinessBase
{
List<T> lst = new List<T>();
foreach (DataRow dr in dt.Rows)
{
T t = new T(dr);
lst.Add(t);
}
return lst;
}
So as you can see, i am trying to dump contents of a Table into an object (via passing a DataRow to the constructor) and then add the object to collection. it complains that T is not a type or namespace it knows about and that I can't use where on a non-generic declaration.
Is this not possible?
There are two big problems:
You can't specify a constructor constraint which takes a parameter
Your method isn't currently generic - it should be PopulateCollection<T> instead of PopulateCollection.
You've already got a constraint that T : BusinessBase, so to get round the first problem I suggest you add an abstract (or virtual) method in BusinessBase:
public abstract void PopulateFrom(DataRow dr);
Also add a parameterless constructor constraint to T.
Your method can then become:
protected List<T> PopulateCollection(DataTable dt)
where T: BusinessBase, new()
{
List<T> lst = new List<T>();
foreach (DataRow dr in dt.Rows)
{
T t = new T();
t.PopulateFrom(dr);
lst.Add(t);
}
return lst;
}
If you're using .NET 3.5, you can make this slightly simpler using the extension method in DataTableExtensions:
protected List<T> PopulateCollection<T>(DataTable dt)
where T: BusinessBase, new()
{
return dt.AsEnumerable().Select(dr =>
{
T t = new T();
t.PopulateFrom(dr);
}.ToList();
}
Alternatively, you could make it an extension method itself (again, assuming .NET 3.5) and pass in a function to return instances:
static List<T> ToList<T>(this DataTable dt, Func<DataRow dr, T> selector)
where T: BusinessBase
{
return dt.AsEnumerable().Select(selector).ToList();
}
Your callers would then write:
table.ToList(row => new Whatever(row));
This assumes you go back to having a constructor taking a DataRow. This has the benefit of allowing you to write immutable classes (and ones which don't have a parameterless constructor) it but does mean you can't work generically without also having the "factory" function.
The only constraint you can specify which allows for creation of new instances is new() - basically, a parameterless constructor. To circumvent this do either:
interface ISupportInitializeFromDataRow
{
void InitializeFromDataRow(DataRow dataRow);
}
protected List<T> PopulateCollection<T>(DataTable dt)
where T : BusinessBase, ISupportInitializeFromDataRow, new()
{
List<T> lst = new List<T>();
foreach (DataRow dr in dt.Rows)
{
T t = new T();
t.InitializeFromDataRow(dr);
lst.Add(t);
}
return lst;
}
Or
protected List<T> PopulateCollection<T>(DataTable dt, Func<DataRow, T> builder)
where T : BusinessBase
{
List<T> lst = new List<T>();
foreach (DataRow dr in dt.Rows)
{
T t = builder(dr);
lst.Add(t);
}
return lst;
}
You probably need to add the new generic constraint on T, as follows:
protected List<T> PopulateCollection<T>(DataTable dt) where T : BusinessBase, new()
...
I can't pass a DataRow into the constructor, but you can solve that by assigning it to a property of BusinessBase
A possible way is:
protected List<T> PopulateCollection<T>(DataTable dt) where T: BusinessBase, new()
{
List<T> lst = new List<T>();
foreach (DataRow dr in dt.Rows)
{
T t = new T();
t.DataRow = dr;
lst.Add(t);
}
return lst;
}
where T: BusinessBase
Should have have restriction of new() I think added
It is possible. I have exactly the same thing in my framework. I had exactly the same problem as you and this is how I solved it. Posting relevant snippets from the framework. If I remember correclty, the biggest problem was requirement to call parameterless constructor.
public class Book<APClass> : Book where APClass : APBase
private DataTable Table ; // data
public override IEnumerator GetEnumerator()
{
for (position = 0; position < Table.Rows.Count; position++)
yield return APBase.NewFromRow<APClass>(Table.Rows[position], this.IsOffline);
}
...
public class APBase ...
{
...
internal static T NewFromRow<T>(DataRow dr, bool offline) where T : APBase
{
Type t = typeof(T);
ConstructorInfo ci;
if (!ciDict.ContainsKey(t))
{
ci = t.GetConstructor(new Type[1] { typeof(DataRow) });
ciDict.Add(t, ci);
}
else ci = ciDict[t];
T result = (T)ci.Invoke(new Object[] { dr });
if (offline)
result.drCache = dr;
return result;
}
In this scenario, base class has static method to instantiate objects of its derived classes using constructor that accepts tablerow.

How can I work around C#'s limitation on calling static functions on a Generic type

I have the following extension method, and would like to make it more generic so I don't have to implement it for every class in our domain.
public static IList<User> ToList(this DataTable table)
{
IList<User> users = new List<User>();
foreach (DataRow row in table.Rows)
users.Add(User.FromDataRow(row));
return users;
}
Is there any way to work around this frustrating limitation?
edit: the below paragraph is bollocks, but I'm keeping it so one of the answers makes sense to future readers:
User, as well as my other classes, implements IDataModel. IDataModel only requires 1 method, FromDataRow(DataRow row). Putting a where into the function prototype obviously doesn't help.
When you only need one method, think Func... perhaps a Func<DataRow, T>
public static IList<T> ToList<T>(this DataTable table,
Func<DataRow,T> converter)
{
IList<T> list = new List<T>();
foreach (DataRow row in table.Rows)
list.Add(converter(row));
return list;
}
Then call table.ToList<User>(User.FromDataRow)
In your example code, you're using a static method to create the user from the DataRow:
foreach (DataRow row in table.Rows)
users.Add(User.FromDataRow(row));
But, you can't use static methods to implement an interface.
Assuming that your interface looks like this:
public interface IDataModel {
void FromDataRow(DataRow row);
}
then your User class will have an instance method FromDataRow(), not a static one.
If your classes have parameterless constructors, then you could write this:
public static IList<T> ToList<T>(this DataTable table)
where T : IDataModel, new()
{
IList<T> results = new List<T>();
foreach (DataRow row in table.Rows)
{
T item = new T();
item.FromDataRow(row);
results.Add(item);
}
return users;
}
The IDataModel constraint on <T> requires the type to implement IDataModel.
The new() constraint on <T> requires the type to have a parameterless constructor.

Categories

Resources