2010-06-03 4 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' быть разными? Решение было бы проще, если бы они были более универсальными, например. 'Key',' Total', 'Percentage'. –

+0

+1 Если это точное представление, то ваши методы малы и делают именно то, что вы ожидаете. Может быть, есть другие части вашего кода, которые в первую очередь выиграют от рефакторинга? –

ответ

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

+1

Nice. Для удобства чтения и во избежание реорганизации всех существующих вызовов функций, я бы, вероятно, по-прежнему привязывал ваш вызов к 'GetDataItems' в функции GetDataItemsByColor (IEnumerable dtos). – Jelly

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

Вам нужно будет переключиться с выражения запроса и преобразовать все, где бы вы ни находились, группировать, упорядочивать и выбирать предложения в lambdas. Затем вы можете создать функцию, которая принимает каждый из них в качестве параметров. Вот пример:

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:

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 
    }); 
Смежные вопросы