对相关列表<T>中的属性排序列表<T>
Sort List<T> on a property in a related List<T>
假设我们有两个 List<T>
。第一个是销售总额列表:
class SalesTotals
{
public Guid EmpID { get; set; }
public string EmpName { get; set; }
public decimal? TotalSales { get; set; }
}
然后我们有另一个按年列出的销售额:
class YearlySales
{
public Guid EmpID { get; set; }
public short SalesYear { get; set; }
public decimal? YearlyTotals { get; set; }
}
这些一起用于创建一个 "cross tab" 报告,其中列出了每位员工的总销售额,后跟每一年的列以及相关的年度销售额。它看起来像这样:
| Name | Total | 2018 | 2017 | 2016 |
+------+-------+------+------+------+
| Joe | 70 | 20 | | 50 |
| Sam | 60 | 30 | 20 | 10 |
| Fred | 50 | 30 | | 20 |
| Bob | 40 | 10 | 15 | 15 |
默认情况下,报告按总销售额排序(没问题)。但如果我们想按个别年份排序,事情就会变得棘手。按 2017 年排序(然后按总数排序):
| Name | Total | 2018 | 2017 | 2016 |
+------+-------+------+------+------+
| Sam | 60 | 30 | 20 | 10 |
| Bob | 40 | 10 | 15 | 15 |
| Joe | 70 | 20 | | 50 |
| Fred | 50 | 30 | | 20 |
我假设我们想要(左)Join
这两个 List<T>
在 EmpID
、where SalesYear == <year to sort by>
然后 OrderBy
YearlyTotals, TotalSales
(因为给定年份的 YearlyTotals 可能不存在,在这种情况下我们仍然需要某种类型的订单)。所以我们还要考虑到可能没有当年的记录可以join(所以需要是left join)
如果我写 SQL 它看起来像这样:
SELECT ST.EmpID, ST.EmpName, ST.TotalSales
FROM SalesTotals AS ST
LEFT JOIN YearlySales AS YS ON ST.EmpID=YS.EmpID
WHERE YS.SalesYear=@SortBySalesYear OR YS.SalesYear IS NULL
ORDER BY YS.YearlySales DESC, ST.TotalSales DESC
我对 Linq 还不够好,无法解决这个问题。事实上,我几乎无处可去(也许试图一次做太多,也许我需要将其分解为单独的步骤,而不是搜索一个班轮)。
那么,有没有办法用 Linq 做到这一点?或者我应该尝试其他类型的方法吗?
注意:我只需要在此处进行 "in place" 排序。我这里没有 need/want 返回不同类型的 List<T>
,只是排序后的 List<SalesTotals>
.
编辑:我更喜欢 Linq "Query Syntax",因为它对我来说更直观(强大的 SQL 背景)。所以我更喜欢使用查询语法而不是方法语法的答案。
编辑:这是一个测试用例设置:
class SalesTotals
{
public int EmpID { get; set; }
public string EmpName { get; set; }
public decimal? TotalSales { get; set; }
}
class YearlySales
{
public int EmpID { get; set; }
public short SalesYear { get; set; }
public decimal? YearlyTotals { get; set; }
}
class TestSort
{
public TestSort()
{
var st = new List<SalesTotals>
{
new SalesTotals() { EmpID = 1, EmpName = "Joe", TotalSales = 70 },
new SalesTotals() { EmpID = 2, EmpName = "Sam", TotalSales = 60 },
new SalesTotals() { EmpID = 3, EmpName = "Fred", TotalSales = 50 },
new SalesTotals() { EmpID = 4, EmpName = "Bob", TotalSales = 40 }
};
var ys = new List<YearlySales>
{
new YearlySales() { EmpID = 1, SalesYear = 2018, YearlyTotals = 20 },
new YearlySales() { EmpID = 2, SalesYear = 2018, YearlyTotals = 30 },
new YearlySales() { EmpID = 3, SalesYear = 2018, YearlyTotals = 30 },
new YearlySales() { EmpID = 4, SalesYear = 2018, YearlyTotals = 10 },
new YearlySales() { EmpID = 2, SalesYear = 2017, YearlyTotals = 20 },
new YearlySales() { EmpID = 4, SalesYear = 2017, YearlyTotals = 15 },
new YearlySales() { EmpID = 1, SalesYear = 2016, YearlyTotals = 10 },
new YearlySales() { EmpID = 2, SalesYear = 2016, YearlyTotals = 15 },
new YearlySales() { EmpID = 3, SalesYear = 2016, YearlyTotals = 50 },
new YearlySales() { EmpID = 4, SalesYear = 2016, YearlyTotals = 20 }
};
st = SortByYear(st, ys, 2017);
}
private List<SalesTotals> SortByYear(List<SalesTotals> salesTotals, List<YearlySales> yearlySales, short sortYear)
{
// return sorted salesTotals by sortYear using both salesTotals and yearlySales joined on EmpID
}
}
您可以像在 SQL 中一样编写它!
var results = from t in totals
join y in years on t.EmpID equals y.EmpID into groupedTable
from p in groupedTable.DefaultIfEmpty()
where y == null || y.SalesYear == year
orderby y.SalesYear, t.TotalSales descending
select t;
快速说明:默认情况下,LINQ 中的联接是内部联接。如果您想要外部联接,则必须使用 DefaultIfEmpty() 调用。
作品类型。需要为销售输入 null
List<YearlySale> YearlySales = new List<YearlySale>() { new YearlySale() { EmpID = 1, Sales = 700, Year = 2018 },
new YearlySale() { EmpID = 1, Sales = 600, Year = 2017 },
new YearlySale() { EmpID = 1, Sales = 500, Year = 2016 },
new YearlySale() { EmpID = 2, Sales = 400, Year = 2018 },
new YearlySale() { EmpID = 2, Sales = null, Year = 2017 },
new YearlySale() { EmpID = 2, Sales = 300, Year = 2016 }
};
List<SalesTotal> SalesTotals = new List<SalesTotal>() { new SalesTotal() { EmpID = 1, EmpName = "stan", TotalSales = 1800 },
new SalesTotal() { EmpID = 2, EmpName = "sally", TotalSales = 700 }
};
var q = from s in SalesTotals
join y18 in YearlySales
on s.EmpID equals y18.EmpID
join y17 in YearlySales
on s.EmpID equals y17.EmpID
join y16 in YearlySales
on s.EmpID equals y16.EmpID
where y18.Year == 2018
where y17.Year == 2017
where y16.Year == 2016
select new { SalesTotal = s, Year18 = y18 == null ? 0 : y18.Year, YearS18 = y18 == null ? 0 : y18.Sales
, Year17 = y17 == null ? 0 : y17.Year, YearS17 = y17 == null ? 0 : y17.Sales
, Year16 = y16 == null ? 0 : y16.Year, YearS16 = y16 == null ? 0 : y16.Sales
};
foreach (var v in q.OrderBy(x => x.SalesTotal.EmpID))
{
Debug.WriteLine($"{v.SalesTotal.EmpID} {v.SalesTotal.EmpName} {v.SalesTotal.TotalSales} {v.YearS18} as y18 {v.YearS17} as y17 {v.YearS16} as y16" );
}
与其直接转换 SQL,我认为将查询分成两部分更清楚一些。
首先,找到 YearlySales
作为年份的排序依据:
var sortYearSales = from ys in yearlySales
where ys.SalesYear == SortBySalesYear
select ys;
然后你可以左连接并排序(因为 ys
可能是 null
,我使用了 null 条件成员 accss 运算符):
var orderedSalesTotals = (from st in salesTotals
join ys in sortYearSales on st.EmpID equals ys.EmpID into ysj
from ys in ysj.DefaultIfEmpty()
orderby ys?.YearSales descending, st.TotalSales descending
select st).ToList();
注意:我将 YearlySales
成员的名称更改为 YearSales
,因为 C# 编译器抱怨该成员与 class 具有相同的名称。
您可以在单个查询中执行此操作,但您必须将第一个查询嵌套到第二个查询中,或者在查询中使用 lambda 语法:
var orderedSalesTotals = (from st in salesTotals
join ys in yearlySales on st.EmpID equals ys.EmpID into ysj
from ys in ysj.Where(y => y.SalesYear == SortBySalesYear).DefaultIfEmpty()
orderby ys?.YearSales descending, st.TotalSales descending
select st).ToList();
假设我们有两个 List<T>
。第一个是销售总额列表:
class SalesTotals
{
public Guid EmpID { get; set; }
public string EmpName { get; set; }
public decimal? TotalSales { get; set; }
}
然后我们有另一个按年列出的销售额:
class YearlySales
{
public Guid EmpID { get; set; }
public short SalesYear { get; set; }
public decimal? YearlyTotals { get; set; }
}
这些一起用于创建一个 "cross tab" 报告,其中列出了每位员工的总销售额,后跟每一年的列以及相关的年度销售额。它看起来像这样:
| Name | Total | 2018 | 2017 | 2016 |
+------+-------+------+------+------+
| Joe | 70 | 20 | | 50 |
| Sam | 60 | 30 | 20 | 10 |
| Fred | 50 | 30 | | 20 |
| Bob | 40 | 10 | 15 | 15 |
默认情况下,报告按总销售额排序(没问题)。但如果我们想按个别年份排序,事情就会变得棘手。按 2017 年排序(然后按总数排序):
| Name | Total | 2018 | 2017 | 2016 |
+------+-------+------+------+------+
| Sam | 60 | 30 | 20 | 10 |
| Bob | 40 | 10 | 15 | 15 |
| Joe | 70 | 20 | | 50 |
| Fred | 50 | 30 | | 20 |
我假设我们想要(左)Join
这两个 List<T>
在 EmpID
、where SalesYear == <year to sort by>
然后 OrderBy
YearlyTotals, TotalSales
(因为给定年份的 YearlyTotals 可能不存在,在这种情况下我们仍然需要某种类型的订单)。所以我们还要考虑到可能没有当年的记录可以join(所以需要是left join)
如果我写 SQL 它看起来像这样:
SELECT ST.EmpID, ST.EmpName, ST.TotalSales
FROM SalesTotals AS ST
LEFT JOIN YearlySales AS YS ON ST.EmpID=YS.EmpID
WHERE YS.SalesYear=@SortBySalesYear OR YS.SalesYear IS NULL
ORDER BY YS.YearlySales DESC, ST.TotalSales DESC
我对 Linq 还不够好,无法解决这个问题。事实上,我几乎无处可去(也许试图一次做太多,也许我需要将其分解为单独的步骤,而不是搜索一个班轮)。
那么,有没有办法用 Linq 做到这一点?或者我应该尝试其他类型的方法吗?
注意:我只需要在此处进行 "in place" 排序。我这里没有 need/want 返回不同类型的 List<T>
,只是排序后的 List<SalesTotals>
.
编辑:我更喜欢 Linq "Query Syntax",因为它对我来说更直观(强大的 SQL 背景)。所以我更喜欢使用查询语法而不是方法语法的答案。
编辑:这是一个测试用例设置:
class SalesTotals
{
public int EmpID { get; set; }
public string EmpName { get; set; }
public decimal? TotalSales { get; set; }
}
class YearlySales
{
public int EmpID { get; set; }
public short SalesYear { get; set; }
public decimal? YearlyTotals { get; set; }
}
class TestSort
{
public TestSort()
{
var st = new List<SalesTotals>
{
new SalesTotals() { EmpID = 1, EmpName = "Joe", TotalSales = 70 },
new SalesTotals() { EmpID = 2, EmpName = "Sam", TotalSales = 60 },
new SalesTotals() { EmpID = 3, EmpName = "Fred", TotalSales = 50 },
new SalesTotals() { EmpID = 4, EmpName = "Bob", TotalSales = 40 }
};
var ys = new List<YearlySales>
{
new YearlySales() { EmpID = 1, SalesYear = 2018, YearlyTotals = 20 },
new YearlySales() { EmpID = 2, SalesYear = 2018, YearlyTotals = 30 },
new YearlySales() { EmpID = 3, SalesYear = 2018, YearlyTotals = 30 },
new YearlySales() { EmpID = 4, SalesYear = 2018, YearlyTotals = 10 },
new YearlySales() { EmpID = 2, SalesYear = 2017, YearlyTotals = 20 },
new YearlySales() { EmpID = 4, SalesYear = 2017, YearlyTotals = 15 },
new YearlySales() { EmpID = 1, SalesYear = 2016, YearlyTotals = 10 },
new YearlySales() { EmpID = 2, SalesYear = 2016, YearlyTotals = 15 },
new YearlySales() { EmpID = 3, SalesYear = 2016, YearlyTotals = 50 },
new YearlySales() { EmpID = 4, SalesYear = 2016, YearlyTotals = 20 }
};
st = SortByYear(st, ys, 2017);
}
private List<SalesTotals> SortByYear(List<SalesTotals> salesTotals, List<YearlySales> yearlySales, short sortYear)
{
// return sorted salesTotals by sortYear using both salesTotals and yearlySales joined on EmpID
}
}
您可以像在 SQL 中一样编写它!
var results = from t in totals
join y in years on t.EmpID equals y.EmpID into groupedTable
from p in groupedTable.DefaultIfEmpty()
where y == null || y.SalesYear == year
orderby y.SalesYear, t.TotalSales descending
select t;
快速说明:默认情况下,LINQ 中的联接是内部联接。如果您想要外部联接,则必须使用 DefaultIfEmpty() 调用。
作品类型。需要为销售输入 null
List<YearlySale> YearlySales = new List<YearlySale>() { new YearlySale() { EmpID = 1, Sales = 700, Year = 2018 },
new YearlySale() { EmpID = 1, Sales = 600, Year = 2017 },
new YearlySale() { EmpID = 1, Sales = 500, Year = 2016 },
new YearlySale() { EmpID = 2, Sales = 400, Year = 2018 },
new YearlySale() { EmpID = 2, Sales = null, Year = 2017 },
new YearlySale() { EmpID = 2, Sales = 300, Year = 2016 }
};
List<SalesTotal> SalesTotals = new List<SalesTotal>() { new SalesTotal() { EmpID = 1, EmpName = "stan", TotalSales = 1800 },
new SalesTotal() { EmpID = 2, EmpName = "sally", TotalSales = 700 }
};
var q = from s in SalesTotals
join y18 in YearlySales
on s.EmpID equals y18.EmpID
join y17 in YearlySales
on s.EmpID equals y17.EmpID
join y16 in YearlySales
on s.EmpID equals y16.EmpID
where y18.Year == 2018
where y17.Year == 2017
where y16.Year == 2016
select new { SalesTotal = s, Year18 = y18 == null ? 0 : y18.Year, YearS18 = y18 == null ? 0 : y18.Sales
, Year17 = y17 == null ? 0 : y17.Year, YearS17 = y17 == null ? 0 : y17.Sales
, Year16 = y16 == null ? 0 : y16.Year, YearS16 = y16 == null ? 0 : y16.Sales
};
foreach (var v in q.OrderBy(x => x.SalesTotal.EmpID))
{
Debug.WriteLine($"{v.SalesTotal.EmpID} {v.SalesTotal.EmpName} {v.SalesTotal.TotalSales} {v.YearS18} as y18 {v.YearS17} as y17 {v.YearS16} as y16" );
}
与其直接转换 SQL,我认为将查询分成两部分更清楚一些。
首先,找到 YearlySales
作为年份的排序依据:
var sortYearSales = from ys in yearlySales
where ys.SalesYear == SortBySalesYear
select ys;
然后你可以左连接并排序(因为 ys
可能是 null
,我使用了 null 条件成员 accss 运算符):
var orderedSalesTotals = (from st in salesTotals
join ys in sortYearSales on st.EmpID equals ys.EmpID into ysj
from ys in ysj.DefaultIfEmpty()
orderby ys?.YearSales descending, st.TotalSales descending
select st).ToList();
注意:我将 YearlySales
成员的名称更改为 YearSales
,因为 C# 编译器抱怨该成员与 class 具有相同的名称。
您可以在单个查询中执行此操作,但您必须将第一个查询嵌套到第二个查询中,或者在查询中使用 lambda 语法:
var orderedSalesTotals = (from st in salesTotals
join ys in yearlySales on st.EmpID equals ys.EmpID into ysj
from ys in ysj.Where(y => y.SalesYear == SortBySalesYear).DefaultIfEmpty()
orderby ys?.YearSales descending, st.TotalSales descending
select st).ToList();