C# 如何使用单个查询进行左连接、分组和选择?

C# 如何使用单个查询进行左连接、分组和选择?,c#,linq,C#,Linq,假设我有以下数据: var workers = new[] { new { Name = "John", Id = 1 }, new { Name = "Greg", Id = 2 }, new { Name = "Jack", Id = 3 }, new { Name = "Josh", Id = 4 }, new { Name = "Jill", Id = 5 }, new { Name = "Jane", Id =

假设我有以下数据:

var workers = new[]
{
    new {  Name = "John",  Id = 1 },
    new {  Name = "Greg",  Id = 2 }, 
    new {  Name = "Jack",  Id = 3 }, 
    new {  Name = "Josh",  Id = 4 },
    new {  Name = "Jill",  Id = 5 },
    new {  Name = "Jane",  Id = 6 }
};

var contracts = new[]
{
    new {  ContractNumber="1",  WorkerId=1, ContractDate = new DateTime(2017,6,30) },
    new {  ContractNumber="2",  WorkerId=2, ContractDate = new DateTime(2017,7,10) },
    new {  ContractNumber="3",  WorkerId=2, ContractDate = new DateTime(2017,7,15) },
    new {  ContractNumber="4",  WorkerId=5, ContractDate = new DateTime(2017,7,20) },
    new {  ContractNumber="5",  WorkerId=1, ContractDate = new DateTime(2017,7,25) }
};
我需要做的是选择合同日期大于或等于以下值的合同数量最少的第一个工人:

var fromDate = new DateTime(2017, 7, 1);
不包括具有以下Id的工人:

int[] exceptWorkerIds = new int[] {1, 4};
如果多个工人的合同最低数量相似,则按字母顺序选择名为的工人

我用下面的方法解决了这个任务

首先,对于每个离开的工人,加入合同。如果协定存在,则my helper属性ContractExists=1,如果不存在,则为0

var query = 
from w in workers.Where(x => !exceptWorkerIds.Contains(x.Id))
join c in contracts.Where(x => x.ContractDate >= fromDate) 
    on w.Id equals c.WorkerId into workerContracts
from wc in workerContracts.DefaultIfEmpty()
select new {WorkerId = w.Id, WorkerName = w.Name, ContractExists = wc == null ? 0: 1};
此查询提供以下结果:

其次,我将获得的结果按WorkerId分组,WorkerName按sum和worker name得到合同和订单数据的总和:

var result = 
(from q in query
group q.ContractExists by new {q.WorkerId, q.WorkerName} into g
orderby g.Sum(), g.Key.WorkerName
select new 
{
    WorkerId = g.Key.WorkerId, 
    WorkerName = g.Key.WorkerName, 
    WorkerContractsCount = g.Sum()
}).ToList().Take(1);

Take(1)给出了结果数据的前1项:

问题:有没有一种方法可以用唯一的查询或任何比我更简单或优雅的方式来完成?如果是,这是否有助于提高查询执行的生产率

而不是执行join(将数据相乘),然后执行group by(在workerContracts.DefaultIfEmpty()中的wc中执行
之前,查询实际使用的是什么)

另一个逻辑基本相同-
workerContracts.Count()
为您提供了所需数量的合同,因此应用所需的订单,接受第一个订单,您就完成了:

var result =
    (from w in workers.Where(x => !exceptWorkerIds.Contains(x.Id))
     join c in contracts.Where(x => x.ContractDate >= fromDate)
         on w.Id equals c.WorkerId into workerContracts
     let workerContractsCount = workerContracts.Count()
     orderby workerContractsCount, w.Name
     select new
     {
         WorkerId = w.Id,
         WorkerName = w.Name,
         WorkerContractsCount = workerContractsCount
     })
    .FirstOrDefault();

由于
Where
s和
Join
可能比

var result = workers
    .Where(w => !exceptWorkerIds.Contains(w.Id))
    .Select(w => new { 
        Name = w.Name, 
        Id = w.Id, 
        Nb = contracts
            .Count(c => c.WorkerId == w.Id && c.ContractDate >= new DateTime(2017,7,1))
    })
    .OrderBy(w => w.Nb).ThenBy(w => w.Name).FirstOrDefault();

if(result != null)
    Console.WriteLine(result.Name);
else
    Console.WriteLine("Result not found");

说明:对于每个工人(我们不想检查的工人除外),我们计算日期晚于或等于
2017,7,1
,然后按此数字和名称排序,并取第一个。

workers.FirstOrDefault(w=>!contracts.Any(c=>c.WorkerId==w.Id))
将为您提供第一个没有任何合同的工人。如果为null,则可以找到具有内部联接的工作进程。不是答案,因为它将导致2个查询,不仅是1个,而且:)有趣。如果不为null,则无需执行第二个查询。是的,在您的示例中,它将使您更快地执行查询。