我有一個下面的 linq 查詢并獲取如下示例中的資料想要洗掉重復項
List<EmployeeSalary> lstEmployeeSalary =
new EmployeeSalaryFactory().GetRelatedObjects(inValue, ddlPayDate, payRollType, payrollSearch)
.Select(m => (EmployeeSalary)m)
.ToList();
例如:
Id Name EmpCode Salary DateOfSalary
-------------------------------------------------------------
1 Item1 IT00001 $100 5/26/2021
2 Item2 IT00002 $200 4/26/2021
3 Item3 IT00003 $150 5/26/2021
1 Item1 IT00001 $100 4/26/2021
3 Item3 IT00003 $150 4/26/2021
輸出
Id Name EmpCode Salary DateOfSalary
-------------------------------------------------------------
1 Item1 IT00001 $100 5/26/2021
2 Item2 IT00002 $200 4/26/2021
3 Item3 IT00003 $150 5/26/2021
uj5u.com熱心網友回復:
如果假設new EmployeeSalaryFactory().GetRelatedObjects(...)回傳EmployeeSalary物件串列:
List<EmployeeSalary> lstEmployeeSalary =
new EmployeeSalaryFactory().GetRelatedObjects(...)
.GroupBy(x => x.Id)
.Select(g => g.OrderByDescending(o => o.DateOfSalary).First());
uj5u.com熱心網友回復:
首先,不要ToList()在你的程式里面做,除非你將使用結果是一個List<EmployeeSalary>. 如果您只想將獲取的資料回傳給呼叫者,請考慮回傳IEnumerable<EmployeeSalary>并讓呼叫者執行 ToList。
這樣做的原因是,如果您的呼叫者不想使用所有獲取的資料,那么將其全部實作將浪費處理能力:
假設您有以下方法來獲取 EmployeeSalaries:
private EmployeeSalaryFactory {get;} = new EmployeeSalaryFactory();
IEnumerable<EmployeeSalary> GetEmployeeSalaries()
{
return this.EmployeeSalaryFactory
.GetRelatedObjects(inValue, ddlPayDate, payRollType, payrollSearch)
.Select(m => (EmployeeSalary)m);
}
可能 inValue、ddlPayDate 等是此方法的引數,但這不在問題范圍內。
現在讓我們使用這個方法:
EmployeeSalary GetSalary(int employeeId)
{
return this.GetEmployeeSalaries()
.Where(salary => salary.EmployeeId == employeeId)
.FirstOrDefault();
}
如果 GetEmployeeSalaries 會回傳一個,List<EmployeeSalary>那么所有的薪水都會被具體化,而呼叫者可能只需要一些。
回到你的問題
我想洗掉重復項
答案取決于您所說的重復項:兩個 EmployeeSalaries 何時相等?如果所有財產都具有相同的價值,或者如果它們具有相同的 Id(但可能不同的薪水),則兩個薪水是否相同。
我假設第一個:應該檢查所有值是否相等
快速解決方案
如果你只需要為這個用法做這個,如果你不需要對其進行大規模的單元測驗,不需要為以后的變化做準備,不希望能夠重用代碼解決類似的問題,可以考慮在選擇之前使用Queryable.Distinct。
的結果
當然,如果資料在您的本地行程中(而不是在資料庫中),您可以使用 IEnumerable 等效項。
var uniqueSalaries = this.EmployeeSalaryFactory
.GetRelatedObjects(inValue, ddlPayDate, payRollType, payrollSearch)
.Select(salary => new
{
// Select all properties that you need to make a Salary:
Id = salary.Id,
Name = salary.EmpCode,
Salary = salary.Salary,
Date = salary.DateOfSalary,
})
.Distinct()
在 Distinct 之前,所選物件為匿名型別。它們有一個默認的相等比較器,它按值進行比較,而不是按參考進行比較。因此,對于每個屬性具有相同值的這種匿名型別的兩個物件被認為是相等的。Distinct 將洗掉重復項。
如果您確實需要結果為IEnumerable<EmployeeSalary>,則需要第二次選擇:
.Select(uniqueSalary => new EmployeeSalary
{
Id = uniqueSalary.Id,
Name = uniqueSalary.Name,
...
});
正確的解決方案
如果輸入資料在您的本地行程中(= 它是 IEnumerable),則您可以使用更多 LINQ 方法,例如具有引數 EqualityComparer 的 Enumerable.Distinct的多載。
In that case, my advice would be to create an Equality comparer for EmployeeSalaries. This will have the advantage that you can reuse the equality comparer for other EmployeeSalary problems. The code will look easier to read. You are prepared for future changes: if you add or remove a property from your definition of equality, for instance if you only need to check the Id, there is only one place that you have to change. You can unit test the comparer: didn't you forget some properties?
private EmployeeSalaryFactory {get;} = new EmployeeSalaryFactory();
private IEqualityComparer<EmployeeSalary> SalaryComparer {get} = ...;
private IEnumerable<EmployeeSalary> GetEmployeeSalaries() { ... see above }
To get the unique salaries:
IEnumerable<EmployeeSalary> uniqueSalaries = this.GetEmployeeSalaries()
.Distinct(this.SalaryComparer);
Did you notice, that because I reuse a lot of code, the specific problem of unique salaries is quite easy to understand.
I cheated a little, I moved the problem to the equality comparer.
IEquality
Creating a reusable equality comparer is fairly straightforward. The advantage is that you can reuse it in all cases where you need to compare EmployeeSalaries. If in future your definition of equality changes, there is only one place that you need to change. Finally: only one place where you need to unit test whether you implemented the proper definition of equality.
public class EmployeeSalaryComparer : EqualityComparer<EmployeeSalary>()
{
public static IEqualityComparer<EmployeeSalary> ByValue {get} = new EmployeeSalaryComparer;
public override bool Equals (EmployeeSalary x, EmployeeSalary y) {...}
public override int GetHashCode (EmployeeSalary x) {...}
}
Usage would be:
IEqualityComparer<EmployeeSalary> salaryComparer = EmployeeSalaryComparer.ByValue;
EmployeeSalary employee1 = ...
EmployeeSalary employee2 = ...
bool equal = salaryComparer.Equals(employee1, employee2);
Implement equality
public override bool Equals (EmployeeSalary x, EmployeeSalary y)
{
Almost all equality comparers start with the following lines:
if (x == null) return y == null; // true if both null
if (y == null) return false; // because x not null
if (Object.ReferenceEquals(x, y) return true; // same object
if (x.GetType() != y.GetType() return false;
After this, the real comparing for equality starts. The implementation depends on what you call equality. You might say: same Id is equal EmployeeSalary. Our aproach is to check all fields, for instance to see if we need to update the database, because some values are changed:
return x.Id == y.Id
&& x.Name == y.Name
&& x.EmpCode == y.EmpCode
&& x.Salary == y.Salary
&& x.DateOfSalary == y.DateOfSalary;
}
Are in your definition the names: "John Doe" and "john doe" equal? And when are EmpCodes equal?
If you think they are not default, or might change in future, consider to add properties to the EmployeeSalaryComparer:
private static IEqualityComparer<string> NameComparer {get} = StringComparer.InvariantCultureIgnoreCase;
private static IEqualityComparer<string> EmpCodeComparer {get} = StringComparer.OrdinalIgnoreCase;
...
The check for equality will end like:
return IdComparer.Equals(x.Id, y.Id)
&& NameComparer.Equals(x.Name, y.Name)
&& EmpCodeComparer.Equals(x.EmpCode, y.EmpCode)
&& SalaryComparer.Equals(x.Salary, y.Salary)
&& DateComparer.Equals(x.DateOfSalary, y.DateOfSalary);
If company policy about names in future changes, then all you have to do is select a different name comparer. And if EmpCode "Boss" is the same as EmpCode "boss": only one place to change the code.
Of course, after spec changes you need to change your unit tests, so they will tell you automatically where you forgot to change the proper equality comparers.
GetHashCode
GetHashCode is used to quickly check for inequality. Keywords: quickly, and inequality. If two Hash codes are different, we know that the object are not equal. It is not the other way round: if two hash codes are equal, we don't know whether the objects are equal.
The hash code is meant to quickly throw away most unequal objects. For instance, in a Distinct method, it would be nice if you could quickly throw away 99% of the objects, so you only have to thoroughly check 1% of the objects for equality.
With EmployeeSalaries we know that if the Id is different, than the Salaries are not equal. It will seldom be that two EmployeeSalaries will have the same Id, but different EmpCode. So by checking the Id only, we throw away most unequal EmployeeSalaries.
How about this:
public override int GetHashCode (EmployeeSalary x)
{
if (x == null) return 9875578; // just a number for null salaries
return x.Id.GetHashCode();
}
Conclusion
- We've discussed why it is better to return IEnumerable instead of ToList.
- We've talked about methods to make your code reusable, easier to read, maintainable, easier to unit test
- We've talked about equality comparers
- We've used Distinct to solver you problem
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/344920.html
標籤:C# asp.net asp.net-mvc 林克
