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.
Related
I have two parent classes: Item and ItemData. From the first, I make items such as WeaponItem and from the second I make corresponding data like WeaponData.
Each item type takes the same type of data in its constructor (example: when creating WeaponItem it needs WeaponData).
I have the following method:
public static T CreateNewItem<T, DT>(ItemData data) where T : Item where DT : ItemData
{
if (data == null) return null;
if (data is DT dt)
{
//determine type of T
Type itemType = typeof(T);
if (itemType = typeof(WeaponItem))
{
//make a new weapon item...
WeaponItem new_weapon = new WeaponItem(dt); //error!
return new_weapon;
}
if (itemType = typeof(ArmorItem))
{
//make a new armor item...
}
//etc...
}
return null;
}
And I call it like so:
CreateNewItem<WeaponItem, WeaponData>(data);
Note: data is of type ItemData or some inherited type (eg. WeaponData).
This method needs two types: one for the Item and one for the ItemData. I have several items (weapons, armors etc...) and I create the item based on the type I give. I want to also check for the data which I do with: data is DT dt to get the WeaponData from the data I pass, or null if the cast fails.
When I try doing WeaponItem new_weapon = new WeaponItem(dt); I get the following error:
Argument 1: cannot convert from 'DT' to 'WeaponData' [Assembly-CSharp]csharp(CS1503)
I tried casting dt to WeaponData like so: WeaponItem new_weapon = new WeaponItem((WeaponData)dt); , but I get a similar error
Cannot convert type 'DT' to 'WeaponData' [Assembly-CSharp]csharp(CS0030)
Similarly, using data in place of dt produces errors.
Is it possible to solve this problem without checking the type every time?
Your design is a bit counter intuitive to me, so let's start with an extremely simple version of the generic method and go from there. This code you can copy and compile.
You can also click this link and just hit the Run button.
I'd say the biggest advantage here is the generic method became one line of code. You can add as many items as you want and never change that method. Whereas your implementation gets more and more complex.
Code:
public class ItemData
{
public string ItemName { get; init; }
}
public class WeaponData : ItemData { }
public class Item
{
public ItemData Data { get; init; }
public Item(ItemData data)
{
Data = data;
}
}
public class WeaponItem : Item
{
public WeaponItem(WeaponData data) : base(data) { }
public override string ToString() => Data.ItemName;
}
public static T CreateNewItem<T, TD>(TD data)
where T : Item
where TD : ItemData
{
return (T)Activator.CreateInstance(typeof(T), data);
}
static void Main(string[] args)
{
var bestWeaponDataEver = new WeaponData() { ItemName = "Dragon Slayer" };
var amazingWeapon = CreateNewItem<WeaponItem, WeaponData>(bestWeaponDataEver);
Console.WriteLine(amazingWeapon);
}
Output:
Dragon Slayer
Another big difference is you're accepting the specific type ItemData which should be the generic type DT. Otherwise why does DT even exist? This might have tripped you up.
public static T CreateNewItem<T, DT>(ItemData data) where T : Item where DT : ItemData
Should be this instead
public static T CreateNewItem<T, DT>(DT data) where T : Item where DT : ItemData
Note the constructor for WeaponItem only accepts WeaponData. So despite the simple generic method, you can't create a weapon with the base ItemData or CakeData or anything else.
If you're not sure how to expand on this comment and I'll try to help further.
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});
}
}
Okay I'm looking for some input, I'm pretty sure this is not currently supported in .NET 3.5 but here goes.
I want to require a generic type passed into my class to have a constructor like this:
new(IDictionary<string,object>)
so the class would look like this
public MyClass<T> where T : new(IDictionary<string,object>)
{
T CreateObject(IDictionary<string,object> values)
{
return new T(values);
}
}
But the compiler doesn't support this, it doesn't really know what I'm asking.
Some of you might ask, why do you want to do this? Well I'm working on a pet project of an ORM so I get values from the DB and then create the object and load the values.
I thought it would be cleaner to allow the object just create itself with the values I give it. As far as I can tell I have two options:
1) Use reflection(which I'm trying to avoid) to grab the PropertyInfo[] array and then use that to load the values.
2) require T to support an interface like so:
public interface ILoadValues
{
void LoadValues(IDictionary values);
}
and then do this
public MyClass<T> where T:new(),ILoadValues
{
T CreateObject(IDictionary<string,object> values)
{
T obj = new T();
obj.LoadValues(values);
return obj;
}
}
The problem I have with the interface I guess is philosophical, I don't really want to expose a public method for people to load the values. Using the constructor the idea was that if I had an object like this
namespace DataSource.Data
{
public class User
{
protected internal User(IDictionary<string,object> values)
{
//Initialize
}
}
}
As long as the MyClass<T> was in the same assembly the constructor would be available. I personally think that the Type constraint in my opinion should ask (Do I have access to this constructor? I do, great!)
Anyways any input is welcome.
As stakx has said, you can't do this with a generic constraint. A workaround I've used in the past is to have the generic class constructor take a factory method that it can use to construct the T:
public class MyClass<T>
{
public delegate T Factory(IDictionary<string, object> values);
private readonly Factory _factory;
public MyClass(Factory factory)
{
_factory = factory;
}
public T CreateObject(IDictionary<string, object> values)
{
return _factory(values);
}
}
Used as follows:
MyClass<Bob> instance = new MyClass<Bob>(dict => new Bob(dict));
Bob bob = instance.CreateObject(someDictionary);
This gives you compile time type safety, at the expense of a slightly more convoluted construction pattern, and the possibility that someone could pass you a delegate which doesn't actually create a new object (which may or may not be a major issue depending on how strict you want the semantics of CreateObject to be).
If you can create common base class for all of T ojects that you are going to pass to MyClass as type parameters than you can do following:
internal interface ILoadValues
{
void LoadValues<TKey, TValue>(IDictionary<TKey, TValue> values);
}
public class Base : ILoadValues
{
void ILoadValues.LoadValues<TKey, TValue>(IDictionary<TKey, TValue> values)
{
// Load values.
}
}
public class MyClass<T>
where T : Base, new()
{
public T CreateObject(IDictionary<string,object> values)
{
ILoadValues obj = new T();
obj.LoadValues(values);
return (T)obj;
}
}
If you cannot have common base class than I think you should go with solution proposed by itowlson.
I'm legitimately curious at how you would load the values of a class without using reflection unless you had methods hardcoded to accomplish it. I'm sure there's another answer, but I'm not too ashamed to say I do not have experience in it. As for something I wrote to auto-load data, I have two base data classes I work from: a single object and then a list. In the single object (BaseDataClass), I have this method.
public virtual void InitializeClass(DataRow dr)
{
Type type = this.GetType();
PropertyInfo[] propInfos = type.GetProperties();
for (int i = 0; i < dr.ItemArray.GetLength(0); i++)
{
if (dr[i].GetType() != typeof(DBNull))
{
string field = dr.Table.Columns[i].ColumnName;
foreach (PropertyInfo propInfo in propInfos)
{
if (field.ToLower() == propInfo.Name.ToLower())
{
// get data value, set property, break
object o = dr[i];
propInfo.SetValue(this, o, null);
break;
}
}
}
}
}
And then in the data list
public abstract class GenericDataList<T> : List<T> where T : BaseDataClass
{
protected void InitializeList(string sql)
{
DataHandler dh = new DataHandler(); // my general database class
DataTable dt = dh.RetrieveData(sql);
if (dt != null)
{
this.InitializeList(dt);
dt.Dispose();
}
dt = null;
dh = null;
}
protected void InitializeList(DataTable dt)
{
if (dt != null)
{
Type type = typeof(T);
MethodInfo methodInfo = type.GetMethod("InitializeClass");
foreach (DataRow dr in dt.Rows)
{
T t = Activator.CreateInstance<T>();
if (methodInfo != null)
{
object[] paramArray = new object[1];
paramArray[0] = dr;
methodInfo.Invoke(t, paramArray);
}
this.Add(t);
}
}
}
}
I'm open to criticism, because no one has ever reviewed this code before. I am the sole programmer where I work, so I do not have others to bounce ideas off of. Thankfully, now I've come across this website!
Edit: You know what? Looking at it now, I don't see why I shouldn't just rewrite that last method as
protected void InitializeList(DataTable dt)
{
if (dt != null)
{
Type type = typeof(T);
foreach (DataRow dr in dt.Rows)
{
T t = Activator.CreateInstance<T>();
(t as BaseDataClass).InitializeClass(dr);
this.Add(t);
}
}
}
I assume that works, although I haven't tested it. No need to use reflection on that part.
I've created generic List and populate with some objects. Then List I mentioned before converted into DataTable to use in DataGridView. Problem is when I want get Row from this grid I have DataRow. I wanted to convert this to my object againt but not sure how to do it. Maybe you could give some example?
Thanks
Well, if you can't or won't use an "ORM" (object-relational mapper, like Linq-to-SQL or NHibernate - that's exactly what these tools do, and do quite well for you), you'll have to do this yourself.
Converting a DataRow into a domain object model is pretty boring code, really:
public Customer ConvertRowToCustomer(DataRow row)
{
Customer result = new Customer();
result.ID = row.Field<int>("ID");
result.Name = row.Field<string>("CustomerName");
..... // and so on
return result;
}
The biggest challenge here is making this rock-solid and handling (or avoiding) all possible errors (like a field being NULL etc.).
Another possibility would be to have a constructor on your domain model object type that would take a DataRow as parameter and construct a new object from it.
Marc
Assuming you're using a class MyObject, defined as follows :
class MyObject
{
public string Foo { get; set; }
public int Foo { get; set; }
}
You could do something like that :
using System.Data.DataSetExtensions;
...
List<MyObject> list = (from row in table.AsEnumerable()
select new MyObject
{
Foo = row.Field<string>("foo"),
Bar = row.Field<int>("bar")
}).ToList();
Why not just put your objects into a BindingList<> rather than a List<>? Then you can skip the converting to DataTable and back again exercise. You may need to implement INotifyPropertyChanged on your objects, but once they are inside a BindingList, changes in the datagrid will automatically be applied to your underlying objects.
Sorting can be handled by either sorting the list manually on column header click, or by inheriting from BindingList<> and implementing the sorting functionality inside it - then clicking on a header automatically sorts the list - no code required.
Well nowadays it is easier using ORMs of course. But if still you're using the old fashion you can go with a pretty easy Extension Class to do the job for you using a little bit of reflection and generic methods and lambda as follows:
public static class MapperExtensionClass
{
public static IEnumerable<MyClassType> ToMyClassTypeEnumerable(this DataTable table)
{
return table.AsEnumerable().Select(r => r.ToMyClassType());
}
public static MyClassType ToMyClassType(this DataRow row)
{
return row.ToObject<MyClassType>();
}
public static T ToObject<T>(this DataRow row) where T: new()
{
T obj = new T();
foreach (PropertyInfo property in typeof(T).GetProperties())
{
if (row.Table.Columns.Contains(property.Name))
{
property.SetValue(obj, property.PropertyType.ToDefault(row[property.Name]));
}
}
return obj;
}
public static object ToDefault(this Type type, object obj)
{
if (type == null)
throw new Exception("Customized exception message");
var method = typeof(MapperExtensionClass)
.GetMethod("ToDefaultGeneric", BindingFlags.Static | BindingFlags.Public);
var generic = method.MakeGenericMethod(type);
return generic.Invoke(null, new object[] { obj });
}
public static T ToDefaultGeneric<T>(object obj)
{
if (obj == null || obj == DBNull.Value)
{
return default(T);
}
else
{
return (T)obj;
}
}
}
You should also remember GridView objects can bind a lot of data source types. So it is your decision from a design point about what you should go with.
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.