How to write this functionality as a generic extension method in C#?
I have the fol开发者_如何学JAVAlowing:
using (var dsProperties = GetDataset(SP_GET_APPLES, arrParams))
{
var apples= dsProperties.Tables[0].AsEnumerable()
.Select(r => new Apple()
{
Color = r[0].ToString(),
Year = r[1].ToString(),
Brand= r[2].ToString()
});
return apples.ToList();
}
Now, I would like to have an extension method on Dataset
to which I can pass the needed Type
as a parameter and get the intended List
back... something like
dsProperties.GetList(Apple);
which can also be used for
using (var dsProperties = GetDataset(SP_GET_ORANGES, arrParams)){
dsProperties.GetList(Orange); }
Is there a way to accomplish this?
How about this?
static IEnumerable<T> GetList<T>(this DataSet dataSet, Func<DataRow, T> mapper) {
return dataSet
.Tables[0]
.AsEnumerable()
.Select(mapper);
}
And usage:
dsProperties.GetList<Apple>(r =>
new Apple {
Color = r[0].ToString(),
Year = r[1].ToString(),
Brand= r[2].ToString()
});
This mapping can well be put in another place as well.
Something like the (untested) following, but it would need a lot of error handling added (if a field is missing, wrong data type, nulls).
public static IEnumerable<T> GetEnumeration<T>(this DataSet dataset) where T: new()
{
return dataset.Tables[0].AsEnumerable()
.Select(r => {
T t = new T();
foreach (var prop in typeof(T).GetProperties())
{
prop.SetValue(t, r[prop.Name]);
}
return t;
});
}
You would use it like dataset.GetEnumeration<Apple>().ToList()
. Note that this uses reflection and could be slow for large data sets, and makes a lot of assumptions, such as each field in the type matching the columns in the data table. Personally I use a repository for each business object which explicitly constructs the object from a data row. More work to set up but in the long run I have a bit more control. You could probably look at an ORM framework like NHibernate as well.
I think your best (and cleanest, as in "reflection-less") bet will be to create a constructor for each involved class (Apple
, Orange
, etc.) that takes a DataRow
and initializes the object based on the row. Then, your code simplifies to dsProperties.Tables[0].AsEnumerable().Select(r => new Apple(r))
. Simplifying it further into a generic extension method will be difficult because you cannot have type constraints that specify the existence of a constructor that takes certain parameters.
If you really want a generic extension method for this, I think you'll have to use the factory pattern, so that the extension method can instantiate a factory that can convert DataRow
s into the desired type. That's gonna be quite a bit of code for (I think) quite little benefit, but I can create an example if you'd like to see it.
Edit: I'd advise you to rather create an extension method that lets you do this: dsProperties.CreateFruits(r => new Apple(r))
. That's about as short as it would be with the extension method you requested. You'll still have to create the constructors, though, so if what you're really after is to save coding on the object constructions, then you'll probably need reflection-based approaches as described in the other answers.
Edit again: @MikeEast beat me to my last suggestion.
Is there a standard naming convention between your stored procedures and your types? If so then you can reflect on the type and retrieve its name then convert that to its stored procedure.
Otherwise, you could have a static dictionary of the type name as a key and the value being the associated stored procedure. Then in your method you would look up the stored procedure after reflecting on the type.
Also I believe you will need to use generics. Something like (it's been a while since i've done generics):
public static IEnumerable<T> GetList<T>(this DataSet ds)
The conversion of columns to properties on your object would also be achieved through reflection. You would loop over the properties on the object and find a matching column to insert the value.
Hope this helps to get you started.
I like MikeEast's approach, but don't like the idea that you have to pass the mapping to every call to GetList
. Try this variation instead:
public static class DataSetEx
{
private static Dictionary<Type, System.Delegate> __maps
= new Dictionary<Type, System.Delegate>();
public static void RegisterMap<T>(this Func<DataRow, T> map)
{
__maps.Add(typeof(T), map);
}
public static IEnumerable<T> GetList<T>(this DataSet dataSet)
{
var map = (Func<DataRow, T>)(__maps[typeof(T)]);
return dataSet.Tables[0].AsEnumerable().Select(map);
}
}
Now, given the classes Apple
& Orange
call the following methods to register the maps:
DataSetEx.RegisterMap<Apple>(r => new Apple()
{
Color = r[0].ToString(),
Year = r[1].ToString(),
Brand= r[2].ToString(),
});
DataSetEx.RegisterMap<Orange>(r => new Orange()
{
Variety = r[0].ToString(),
Location = r[1].ToString(),
});
Then you can just call GetList
without the map like so:
var ds = new DataSet();
// load ds here...
// then
var apples = ds.GetList<Apple>();
// or
var oranges = ds.GetList<Orange>();
That gives some nice code separation without the need for reflection or repetition.
This also doesn't stop you using a hybrid approach of using reflection in cases where a map hasn't explicitly been defined. You kind of can get the best of both worlds.
精彩评论