我有以下内容:
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();
}
现在,我希望Dataset
上有一个扩展方法,我可以将所需的Type
作为参数传递给我,并将预期的List
恢复为...... / p>
dsProperties.GetList(Apple);
也可以用于
using (var dsProperties = GetDataset(SP_GET_ORANGES, arrParams)){
dsProperties.GetList(Orange); }
有没有办法实现这个目标?
答案 0 :(得分:7)
这个怎么样?
static IEnumerable<T> GetList<T>(this DataSet dataSet, Func<DataRow, T> mapper) {
return dataSet
.Tables[0]
.AsEnumerable()
.Select(mapper);
}
用法:
dsProperties.GetList<Apple>(r =>
new Apple {
Color = r[0].ToString(),
Year = r[1].ToString(),
Brand= r[2].ToString()
});
这种映射也可以放在另一个地方。
答案 1 :(得分:2)
类似于(未经测试的)跟随,但它需要添加大量错误处理(如果缺少字段,错误的数据类型,空值)。
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;
});
}
您可以像dataset.GetEnumeration<Apple>().ToList()
一样使用它。请注意,这使用反射,对于大型数据集可能会很慢,并且会做出许多假设,例如匹配数据表中列的类型中的每个字段。就个人而言,我为每个业务对象使用一个存储库,该存储库从数据行显式构造对象。建立更多的工作,但从长远来看,我有更多的控制权。你可能也可以看一下像NHibernate这样的ORM框架。
答案 2 :(得分:1)
我认为你最好的(也是最干净的,如“无反思”)赌注将为每个涉及的类(Apple
,Orange
等)创建一个构造函数,该构造函数需要{ {1}}并根据行初始化对象。然后,您的代码简化为DataRow
。将其进一步简化为通用扩展方法将很困难,因为您不能具有指定存在具有某些参数的构造函数的类型约束。
如果你真的想要一个通用的扩展方法,我认为你必须使用工厂模式,这样扩展方法可以实例化一个可以将dsProperties.Tables[0].AsEnumerable().Select(r => new Apple(r))
转换为所需类型的工厂。这将是相当多的代码(我认为)相当少的好处,但如果你想看到它,我可以创建一个例子。
编辑:我建议您改为创建一个允许您执行此操作的扩展方法:DataRow
。这与您请求的扩展方法一样短。但是,您仍然需要创建构造函数,因此,如果您真正想要的是在对象构造上保存编码,那么您可能需要基于反射的方法,如其他答案中所述。
再次编辑:@MikeEast打败了我的最后一个建议。
答案 3 :(得分:0)
存储过程和类型之间是否存在标准命名约定?如果是,那么您可以反思类型并检索其名称,然后将其转换为其存储过程。
否则,您可以将类型名称的静态字典作为键,并将值作为关联的存储过程。然后在您的方法中,您将在反映类型后查找存储过程。
另外我相信你需要使用泛型。有点像(自从我做了仿制药以来已经有一段时间了):
public static IEnumerable<T> GetList<T>(this DataSet ds)
也可以通过反射将列转换为对象上的属性。您将遍历对象上的属性并找到匹配的列以插入值。
希望这有助于您入门。
答案 4 :(得分:0)
我喜欢MikeEast的方法,但不喜欢你必须将映射传递给GetList
的每个调用的想法。请尝试此变体:
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);
}
}
现在,鉴于课程Apple
&amp; Orange
调用以下方法来注册地图:
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(),
});
然后你就可以在没有地图的情况下调用GetList
:
var ds = new DataSet();
// load ds here...
// then
var apples = ds.GetList<Apple>();
// or
var oranges = ds.GetList<Orange>();
这提供了一些很好的代码分离,而无需反射或重复。
这也不会阻止您在未明确定义地图的情况下使用反射的混合方法。你可以充分利用两个世界。