我目前正在开发一个.NET Core应用程序。
我需要根据以下要求筛选LINQ查询:
- Id:如果没有ContactId,只选择Id一次(1(
- Id和ContactId:如果存在,则筛选Id(1(并添加Id和Contact Id对(1,1(
- Id和ContactId对必须唯一,但可以有以下变化:(1,1(、(1,5(
- 必须删除空对象
public class SearchResult
{
public int? Id {get; set;}
public int? ContactId {get; set;}
}
public class Program
{
public static void Main()
{
var searchResults = new List<SearchResult>
{
new SearchResult { Id = 1 },
new SearchResult { },
new SearchResult { Id = 2 }, // yes
new SearchResult { Id = 3 }, // yes
new SearchResult { Id = 4 }, // yes
new SearchResult { Id = 5 },
new SearchResult { Id = 5, ContactId = 3 }, // yes
new SearchResult { Id = 1, ContactId = 1 }, // yes
new SearchResult { Id = 1, ContactId = 5 }, // yes
new SearchResult { Id = 8, ContactId = 4 }, // yes
new SearchResult { Id = 1 },
new SearchResult { Id = 2 },
new SearchResult { Id = 10 }, // yes
new SearchResult { Id = 11 }, // yes
new SearchResult { Id = 12 }, // yes
};
// unfortunately this LINQ query does not work correctly:
var result = searchResults
.OrderBy(x => x.Id)
.ThenBy(x => x.ContactId)
.GroupBy(p => new { p.Id, p.ContactId })
.Select(x => x.First());
foreach(var sr in result){
Console.WriteLine(sr.Id + " " + sr.ContactId);
}
}
预期结果应该是:
1 1
1 5
2
3
4
5 3
8 4
10
11
12
不幸的是,我的LINQ查询无法正常工作。
你知道如何解决这个问题并根据规则过滤LINQ查询吗?
我建议这样做。在这里我们GroupBy
,然后分析每组。最后,我们将group
s压平为IEnumerable<SearchResult>
(请,拨弄(
为了删除重复项,我们应该知道如何比较相等的项,然后调用Distinct
。我们可以为此实现IEqualityComparer:
private class MyEqualityComparer : IEqualityComparer<SearchResult> {
public bool Equals(SearchResult x, SearchResult y) {
if (ReferenceEquals(x, y))
return true;
else if (null == x || null == y)
return false;
return x.Id == y.Id && x.ContactId == y.ContactId;
}
public int GetHashCode(SearchResult obj) => (obj != null && obj.Id.HasValue)
? obj.Id.Value
: 0;
}
最终代码:
var result = searchResults
.Where(item => item.ContactId.HasValue || item.Id.HasValue)
.Distinct(new MyEqualityComparer()) // remove duplicates
.GroupBy(item => item.Id)
.Select(group => group.Any(item => item.ContactId.HasValue)
? group.Where(item => item.ContactId.HasValue)
: group.Take(1))
.SelectMany(group => group);
让我们看看:
Console.WriteLine(string.Join(Environment.NewLine, result
.Select(item => $"{item.Id} {item.ContactId}")));
结果:
1 1
1 5
2
3
4
5 3
8 4
10
11
12
Dmitry Bychenko的解决方案很好,除了一个错误:
它不会删除重复的配对。
编辑现在不再是这种情况,错误已经纠正。
根据,这似乎是强制性的
Id和ContactId对必须是唯一的,但可以像(1,1(,(1,5(
为此,您可以使用Distinct
运算符,但由于SearchResult是一个类,Distinct
不会比较每个实例的值,而是比较其引用。因此,我只需将每个SearchResult投影到一个值元组中,就可以快速比较值而不是引用。
var result = searchResults
.Where(e => e.ContactId.HasValue || e.Id.HasValue)
.Select(e => (Id: e.Id, ContactId: e.ContactId))
.GroupBy(e => e.Id)
.Select(g => g.Any(e => e.ContactId.HasValue)
? g.Where(e => e.ContactId.HasValue).Distinct()
: g.Take(1))
.SelectMany(g => g)
.Select(e => new SearchResult
{
Id = e .Id,
ContactId = e.ContactId
});
此外,您没有指定SearchResult具有ContactId而没有Id的情况。当前代码接受这样一对的行为。
如果你想过滤这些,只需更改这行
.Where(e => e.ContactId.HasValue || e.Id.HasValue)
通过
.Where(e => e.Id.HasValue)
下面是一个完整的Linqpad查询供您尝试。我添加了重复项和带有空Id和非空ContactId 的SearchResults
public static void Main()
{
var searchResults = new List<SearchResult>
{
new SearchResult { Id = 1 },
new SearchResult { },
new SearchResult { ContactId = 45}, // IS accepted, but the behavior was not specified.
new SearchResult { ContactId = 45},
new SearchResult { ContactId = 42},
new SearchResult { ContactId = 45},
new SearchResult { ContactId = 45},
new SearchResult { Id = 2 }, // yes
new SearchResult { Id = 3 }, // yes
new SearchResult { Id = 4 }, // yes
new SearchResult { Id = 5 },
new SearchResult { Id = 5 },
new SearchResult { Id = 5 },
new SearchResult { Id = 5 },
new SearchResult { Id = 5, ContactId = 3 }, // yes
new SearchResult { Id = 1, ContactId = 1 }, // yes
new SearchResult { Id = 1, ContactId = 5 }, // yes
new SearchResult { Id = 8, ContactId = 4 }, // yes
new SearchResult { Id = 8, ContactId = 4 },
new SearchResult { Id = 8, ContactId = 4 },
new SearchResult { Id = 8, ContactId = 4 },
new SearchResult { Id = 1 },
new SearchResult { Id = 2 },
new SearchResult { Id = 10 }, // yes
new SearchResult { Id = 11 }, // yes
new SearchResult { Id = 12 }, // yes
};
var result = searchResults
.Where(e => e.ContactId.HasValue || e.Id.HasValue)
.Select(e => (Id: e.Id, ContactId: e.ContactId))
.GroupBy(e => e.Id)
.Select(g => g.Any(e => e.ContactId.HasValue)
? g.Where(e => e.ContactId.HasValue).Distinct()
: g.Take(1))
.SelectMany(g => g)
.Select(e => new SearchResult
{
Id = e .Id,
ContactId = e.ContactId
})
.Dump();
}
public class SearchResult
{
public int? Id { get; set; }
public int? ContactId { get; set; }
}
编辑根据请求:
可以去掉元组投影,但仍然有Distinct
方法的好处。
一种解决方案是让SearchResult类实现IEquatable接口:
public class SearchResult : IEquatable<SearchResult>
{
public int? Id { get; set; }
public int? ContactId { get; set; }
public bool Equals(SearchResult other)
{
return Id == other.Id && ContactId == other.ContactId;
}
public override int GetHashCode()
{
int hashId = Id == null ? 0 : Id.GetHashCode();
int hashContactId = ContactId == null ? 0 : ContactId.GetHashCode();
return hashId ^ hashContactId;
}
}
然后将查询简化为:
var result = searchResults
.Where(e => e.ContactId.HasValue || e.Id.HasValue)
.GroupBy(e => e.Id)
.Select(g => g.Any(e => e.ContactId.HasValue)
? g.Where(e => e.ContactId.HasValue).Distinct()
: g.Take(1))
.SelectMany(g => g);