2010-06-03 55 views
8

我想弄清楚如何很好地重构这个LINQ代码。此代码和其他类似的代码在同一个文件以及其他文件中重复。有时被操纵的数据是相同的,有时数据会改变,逻辑保持不变。如何重构这个重复的LINQ代码?

下面是在不同对象的不同字段上操作的重复逻辑示例。

public IEnumerable<FooDataItem> GetDataItemsByColor(IEnumerable<BarDto> dtos) 
{ 
    double totalNumber = dtos.Where(x => x.Color != null).Sum(p => p.Number); 
    return from stat in dtos 
      where stat.Color != null 
      group stat by stat.Color into gr 
      orderby gr.Sum(p => p.Number) descending 
      select new FooDataItem 
      { 
       Color = gr.Key, 
       NumberTotal = gr.Sum(p => p.Number), 
       NumberPercentage = gr.Sum(p => p.Number)/totalNumber 
      }; 
} 

public IEnumerable<FooDataItem> GetDataItemsByName(IEnumerable<BarDto> dtos) 
{ 
    double totalData = dtos.Where(x => x.Name != null).Sum(v => v.Data); 
    return from stat in dtos 
      where stat.Name != null 
      group stat by stat.Name into gr 
      orderby gr.Sum(v => v.Data) descending 
      select new FooDataItem 
      { 
       Name = gr.Key, 
       DataTotal = gr.Sum(v => v.Data), 
       DataPercentage = gr.Sum(v => v.Data)/totalData 
      }; 
} 

任何人都有很好的重构方式?

+0

“FooDataItem”中的属性名必须不同吗?如果更通用的话,解决方案会更简单。 '钥匙','总计','百分比'。 – 2010-06-03 14:01:57

+0

+1如果这是一个确切的表示,那么你的方法很小,并做你期望的。也许你的代码的其他部分会首先受益于重构? – 2010-06-03 14:03:55

回答

10

事情是这样的:

public IEnumerable<FooDataItem> GetDataItems<T>(IEnumerable<BarDto> dtos, 
    Func<BarDto, T> groupCriteria, 
    Func<BarDto, double> dataSelector, 
    Func<T, double, double, FooDataItem> resultFactory) 
{ 
    var validDtos = dtos.Where(d => groupCriteria(d) != null); 
    double totalNumber = validDtos.Sum(dataSelector); 

    return validDtos 
     .GroupBy(groupCriteria) 
     .OrderBy(g => g.Sum(dataSelector)) 
     .Select(gr => resultFactory(gr.Key, 
            gr.Sum(dataSelector), 
            gr.Sum(dataSelector)/totalNumber)); 
} 

在你的榜样,你可以这样调用它:

GetDataItems(
    x => x.Color, // the grouping criterion 
    x => x.Number, // the value criterion 
    (key, total, pct) => 
     new FooDataItem { 
      Color = key, NumberTotal = total, NumberPercentage = pct }); 

如果你改变FooDataItem更加通用,它会更容易。

+1

这是美丽的代码。 – Femaref 2010-06-03 14:14:15

+1

不错。为了可读性和避免重构所有现有的函数调用,我可能仍然会将您的调用包装在GetDataItemsByColor(IEnumerable dtos)函数中的GetDataItems中。 – Jelly 2010-06-03 16:31:35

1

我认为,如果你重构了这一点,那么阅读将比你已经阅读的更困难。我所能想到的任何事情都涉及动态Linq,或者修改或封装BarDto以使某种专门的项目仅用于分组。

3

我不会使用这种查询语法,使用方法链。

public IEnumerable<FooDataItem> GetDataItems(IEnumerable<BarDto> dtos, Func<BarDto, object> key, Func<BarDto, object> data) 
{ 
    double totalData = dtos.Where(d => key(d) != null).Sum(data); 
    return dtos.Where(d => key(d) != null) 
      .GroupBy(key) 
      .OrderBy(d => d.Sum(data)) 
      .Select(
       o => new FooDataItem() 
       { 
       Key = o.Key, 
       Total = o.Sum(data), 
       Percentage = o.sum(data)/totalData 
       }); 
} 

(没有编译器等编写)。

就我个人而言,我不会重构它,因为这样会使代码变得不那么容易理解。

2

您需要从查询表达式切换并将所有的where,group by,order by和select子句转换为lambda表达式。然后你可以创建一个接受每个参数的函数。下面是一个例子:

private static IEnumerable<FooDataItem> GetData<T>(IEnumerable<Foo> foos, Func<Foo, bool> where, Func<Foo, T> groupby, Func<IGrouping<T, Foo>, T> orderby, Func<IGrouping<T, Foo>, FooDataItem> select) 
{ 
    var query = foos.Where(where).GroupBy(groupby).OrderBy(orderby).Select(select); 
    return query; 
} 

基于该代码

class Foo 
{ 
    public int Id { get; set; } 
    public int Bar { get; set; } 
} 

...

List<Foo> foos = new List<Foo>(); // populate somewhere 

Func<Foo, bool> where = f => f.Id > 0; 
Func<Foo, int> groupby = f => f.Id; 
Func<IGrouping<int, Foo>, int> orderby = g => g.Sum(f => f.Bar); 
Func<IGrouping<int, Foo>, FooDataItem> select = g => new FooDataItem { Key = g.Key, BarTotal = g.Sum(f => f.Bar) }; 

var query = GetData(foos, where, groupby, orderby, select); 
1

这里是哪些因素出每个查询的相似部分的扩展方法:

public static IEnumerable<TDataItem> GetDataItems<TData, TDataItem>(
    this IEnumerable<BarDto> dtos, 
    Func<BarDto, TData> dataSelector, 
    Func<BarDto, double> numberSelector, 
    Func<TData, double, double, TDataItem> createDataItem) 
    where TData : class 
{ 
    var eligibleDtos = dtos.Where(dto => dataSelector(dto) != null); 

    var totalNumber = eligibleDtos.Sum(numberSelector); 

    return 
     from dto in eligibleDtos 
     group dto by dataSelector(dto) into dtoGroup 
     let groupNumber = dtoGroup.Sum(numberSelector) 
     orderby groupNumber descending 
     select createDataItem(dtoGroup.Key, groupNumber, groupNumber/totalNumber); 
} 

你会用它l ike this:

var itemsByName = dtos.GetDataItems(
    dto => dto.Name, 
    dto => dto.Data, 
    (name, groupTotal, groupPercentage) => new FooDataItem 
    { 
     Name = name, 
     NumberTotal = groupTotal, 
     NumberPercentage = groupPercentage 
    }); 

var itemsByColor = dtos.GetDataItems(
    dto => dto.Color, 
    dto => dto.Number, 
    (color, groupTotal, groupPercentage) => new FooDataItem 
    { 
     Color = color, 
     DataTotal = groupTotal, 
     DataPercentage = groupPercentage 
    });