如何在C#中将此功能编写为通用扩展方法?

时间:2011-03-14 23:23:26

标签: c# linq dataset extension-methods anonymous-types

我有以下内容:

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

有没有办法实现这个目标?

5 个答案:

答案 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)

我认为你最好的(也是最干净的,如“无反思”)赌注将为每个涉及的类(AppleOrange等)创建一个构造函数,该构造函数需要{ {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>();

这提供了一些很好的代码分离,而无需反射或重复。

这也不会阻止您在未明确定义地图的情况下使用反射的混合方法。你可以充分利用两个世界。