【问题标题】:How to write this functionality as a generic extension method in C#?如何将此功能编写为 C# 中的通用扩展方法?
【发布时间】:2011-07-15 10:01:20
【问题描述】:

我有以下几点:

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...类似

dsProperties.GetList(Apple); 

也可以用于

using (var dsProperties = GetDataset(SP_GET_ORANGES, arrParams)){     
dsProperties.GetList(Orange); }

有没有办法做到这一点?

【问题讨论】:

  • 您可以使用反射和/或某种工厂来完成某些事情,但通常 C# 并不擅长这种事情。
  • 你的苹果和橙子实现相同的接口吗?
  • 您必须在调用中提供类型属性的映射。没有办法(除了可以进行映射的反射之外)。
  • @Maxim Gueivandov No. Apples 和 Oranges 是离散的 DTO

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


【解决方案1】:

您的存储过程和类型之间是否有标准命名约定?如果是这样,那么您可以反映类型并检索其名称,然后将其转换为其存储过程。

否则,您可以将类型名称的静态字典作为键,并将值作为关联的存储过程。然后在您的方法中,您将在反映类型后查找存储过程。

另外,我相信您将需要使用泛型。类似的东西(自从我完成泛型以来已经有一段时间了):

public static IEnumerable<T> GetList<T>(this DataSet ds)

也可以通过反射将列转换为对象的属性。您将遍历对象的属性并找到匹配的列来插入值。

希望这有助于您入门。

【讨论】:

    【解决方案2】:

    我认为您最好的(也是最干净的,如“无反射”)的赌注是为每个涉及的类(AppleOrange 等)创建一个构造函数,该构造函数接受 DataRow 并初始化基于行的对象。然后,您的代码将简化为 dsProperties.Tables[0].AsEnumerable().Select(r =&gt; new Apple(r))。将其进一步简化为通用扩展方法将很困难,因为您不能有类型约束来指定存在带有某些参数的构造函数。

    如果你真的想要一个通用的扩展方法,我认为你必须使用工厂模式,以便扩展方法可以实例化一个可以将DataRows 转换为所需类型的工厂。这将是相当多的代码(我认为)几乎没有什么好处,但如果你愿意,我可以创建一个示例。

    编辑:我建议您创建一个扩展方法来让您执行此操作:dsProperties.CreateFruits(r =&gt; new Apple(r))。这与您请求的扩展方法一样短。但是,您仍然必须创建构造函数,因此,如果您真正想要的是节省对象构造的编码,那么您可能需要其他答案中描述的基于反射的方法。

    再次编辑:@MikeEast 在我最后的建议中击败了我。

    【讨论】:

      【解决方案3】:

      类似于(未经测试的)以下内容,但需要添加大量错误处理(如果缺少字段、错误的数据类型、空值)。

      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&lt;Apple&gt;().ToList() 一样使用它。请注意,这使用反射并且对于大型数据集可能会很慢,并且会做出很多假设,例如类型中的每个字段都匹配数据表中的列。我个人为每个业务对象使用一个存储库,该存储库从数据行显式构造对象。更多的工作需要设置,但从长远来看,我有更多的控制权。你也可以看看像 NHibernate 这样的 ORM 框架。

      【讨论】:

      • 这当然意味着对象的属性名称与数据库返回的字段匹配。
      • @MikeEast 没错,明确地从数据行构造每种类型可以更好地控制它,而不是依赖于那个假设。或者,您可以为 Apple 中的每个字段添加一个属性,将字段映射到列,但即使在转换 Enums 时也会很痛苦。
      • 我认为约定优于配置,因此您的方法以及提供 CLRishly 命名字段的 SP 将是最佳选择。除了使用数据集以外的任何东西,就是这样。 EF、NHib、L2S 甚至等等。
      【解决方案4】:

      这个怎么样?

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

      这个映射也可以放在另一个地方。

      【讨论】:

        【解决方案5】:

        我喜欢 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);
            }
        }
        

        现在,给定类 AppleOrange 调用以下方法来注册地图:

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

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

        这也不会阻止您在未明确定义映射的情况下使用使用反射的混合方法。你可以两全其美。

        【讨论】:

          猜你喜欢
          • 2015-09-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-09
          • 2015-08-13
          • 2019-12-09
          • 2018-09-03
          • 1970-01-01
          相关资源
          最近更新 更多