2010-06-03 57 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 
    });