具有groubBy的LINQ筛选器查询



我目前正在开发一个.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,然后分析每组。最后,我们groups压平为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);

最新更新