我试图弄清楚如何很好地重构这个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 :(得分:11)
这样的事情:
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 :(得分: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 :(得分:2)
您需要从查询表达式切换并将所有where,group by,order by和select子句转换为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);
答案 3 :(得分:1)
我认为,如果你重构这个,那么阅读比你已经更难阅读。我能想到的任何一件事都涉及动态Linq,或修改或封装BarDto以使某种特殊项目只用于分组。
答案 4 :(得分: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);
}
您可以这样使用它:
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
});