如何使用两个相关表优化简单的LINQ查询?

时间:2017-07-26 22:24:15

标签: c# linq

我有两个相关的表,结构如下:

'患者':

{ Id = 1, Surname = Smith998 }
...
{ Id = 1000, Surname = Smith1000 }

,第二个是'接待':

{ PatientId = 1, ReceptionStart = 3/3/2017 1:14:00 AM }
{ PatientId = 1, ReceptionStart = 1/7/2016 1:14:00 AM }
...
{ PatientId = 1000, ReceptionStart = 1/23/2017 1:14:00 AM }

这些表不是来自数据库,而是使用以下示例代码生成的:

        var rand = new Random();
        var receptions = Enumerable.Range(1, 1000).SelectMany(pid => Enumerable.Range(1, rand.Next(0, 10)).Select(rid => new { PatientId = pid, ReceptionStart = DateTime.Now.AddDays(-rand.Next(1, 500)) })).ToList();
        var patients = Enumerable.Range(1, 1000).Select(pid => new { Id = pid, Surname = string.Format("Smith{0}", pid) }).ToList();

问题是在2017年1月1日之前选择接受患者的最佳方式是什么?

因为我可以这样写:

        var cured_receptions = (from r in receptions where r.ReceptionStart < new DateTime(2017, 7, 1) select r.PatientId).Distinct();
        var cured_patients = from p in patients where cured_receptions.Contains(p.Id) select p;

但我不清楚'cured_receptions.Contains(p.Id)'代码到底是做什么的?它是否只是遍历搜索Id的所有患者,或者在数据库中使用类似索引的东西?可以使用cure_receptions.ToDictionary()或类似的东西来帮助吗?

2 个答案:

答案 0 :(得分:0)

  

但我不清楚什么&#39; cured_receptions.Contains(p.Id)&#39;代码实际上呢?它是否只是遍历搜索Id的所有患者,或者在数据库中使用像索引这样的东西?

案例1:与数据库交互

如果您正在与数据库交互,那么在您通过调用ToList()或通过迭代cured_patients中的项目来执行第二个查询之前,不会向数据库发送任何查询。发送到数据库的查询将是这样的:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Surname] AS [Surname]
FROM [dbo].[Patients] AS [Extent1]
WHERE  EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Receptions] AS [Extent2]
    WHERE ([Extent2].[ReceptionStart] < 
    convert(datetime2, '2017-07-01 00:00:00.0000000', 121)) 
    AND ([Extent2].[PatientId] = [Extent1].[Id])
)
  

它会使用任何指数吗?

如果PatientIdIdReceptionStart被编入索引,那么数据库服务器将使用它们。

案例2:与内存中的项目互动

对于第一个查询,它将迭代所有receptions,找到ReceptionStart在给定日期之前的那些,选择PatientId,然后删除任何重复的PatientId(或多个)。

然后是下面的第二个查询:

var cured_patients = 
   from p in patients 
   where cured_receptions.Contains(p.Id) 
   select p;

将迭代patients中的每个项目,并查看Id中是否找到该项目的cured_receptions。对于Id中找到cured_receptions的所有项目,它会选择这些项目。 Contains只会返回truefalse

答案 1 :(得分:0)

重新开始假设内存中的所有内容......

  1. cured_receptions调用之前,您的Contains不会被评估,因此在该变量定义的末尾使用put .ToList()会更有效率(大约快100倍) )。

  2. LINQ没有“搜索” - Contains正在进行搜索。如果你想使用像二进制搜索或更好的东西,哈希表,你必须创建它。如果您使用HashSet<int>,那么您将获得另外47倍的加速。关闭Distinct(因为HashSet会处理)会节省15%。

  3. 在变量中记住常量而不是随时创建常量(new DateTime ...)可能会节省更多。即使大大增加您的随机数据也不会花费足够的时间来告诉HashSet

  4. 使用join比初始查询更快,但您的查询与HashSet相结合最快。

  5. 所以最快的代码是:

    var cured_receptions = new HashSet<int>((from r in receptions where r.ReceptionStart < endDateTime select r.PatientId));
    var cured_patients = from p in patients where cured_receptions.Contains(p.Id) select p;
    

    注意:我使用LINQPad生成时序和示例数据。我更改了您的日期参数,因为您的值使大多数接待都匹配。

    以下是我的LINQPad的代码:

    var rand = new Random();
    var begin = DateTime.Now;
    var receptions = Enumerable.Range(1, 100000).SelectMany(pid => Enumerable.Range(1, rand.Next(0, 100)).Select(rid => new { PatientId = pid, ReceptionStart = begin.AddDays(-rand.Next(1, 180)) })).ToList();
    var patients = Enumerable.Range(1, 100000).Select(pid => new { Id = pid, Surname = string.Format("Smith{0}", pid) }).ToList();
    
    var startTime = Util.ElapsedTime;
    var endDateTime = new DateTime(2017, 5, 1);
    //var cured_receptions = (from r in receptions where r.ReceptionStart < new DateTime(2017, 5, 1) select r.PatientId).Distinct().ToList();
    //var cured_receptions = (from r in receptions where r.ReceptionStart < new DateTime(2017, 5, 1) select r.PatientId).Distinct();
    //var cured_receptions = new HashSet<int>((from r in receptions where r.ReceptionStart < new DateTime(2017, 5, 1) select r.PatientId).Distinct());
    //var cured_receptions = new HashSet<int>((from r in receptions where r.ReceptionStart < endDateTime select r.PatientId).Distinct());
    //var cured_receptions = new HashSet<int>((from r in receptions where r.ReceptionStart < new DateTime(2017, 5, 1) select r.PatientId));
    var cured_receptions = new HashSet<int>((from r in receptions where r.ReceptionStart < endDateTime select r.PatientId));
    var cured_patients = from p in patients where cured_receptions.Contains(p.Id) select p;
    
    //  var cured_patients = (from r in receptions
    //                       where r.ReceptionStart < endDateTime
    //                       join p in patients on r.PatientId equals p.Id
    //                       select p).Distinct();
    
    //  var cured_patients = from p in patients
    //                       join r in receptions on p.Id equals r.PatientId into rj
    //                       where rj.Any(r => r.ReceptionStart < endDateTime)
    //                       select p;
    
    cured_patients.Count().Dump();
    var endTime = Util.ElapsedTime;
    
    (endTime - startTime).Dump("Elapsed");