【问题标题】:Create custom objects from IQueryable without loading everything into memory从 IQueryable 创建自定义对象而不将所有内容加载到内存中
【发布时间】:2019-11-24 05:20:30
【问题描述】:

这是this question 的后续问题。你应该先读一下。

感谢this answer,我现在创建了一个可以返回正确条目的查询。见:

IQueryable<Data> onePerHour = dataLastWeek
    .Where(d => 
        !dataLastWeek
        .Any(d2 =>
            d2.ArchiveTime.Date == d.ArchiveTime.Date &&
            d2.ArchiveTime.Hour == d.ArchiveTime.Hour &&
            d2.ArchiveTime < d.ArchiveTime));

现在为了处理条目并将它们显示在图表上,我只需要模型类Data 的一两个属性。用例是这样的:

List<Data> actualData = onePerHour.ToList();

var tempCTupels = new List<TimeTupel<float>>();
tempCTupels.AddRange(actualData.Select(d => new TimeTupel<float>(d.ArchiveTime, d.TempC)));

var co2Tupels = new List<TimeTupel<float>>();
tempCTupels.AddRange(actualData.Select(d => new TimeTupel<float>(d.ArchiveTime, d.CO2Percent)));

TimeTupel 非常简单,定义如下:

public class TimeTupel<TData>
{
    public TimeTupel(DateTime time, TData yValue)
    {
        Time = time;
        YValue = yValue;
    }

    public DateTime Time { get; set; }
    public TData YValue { get; set; }
}

问题

目前actualdataList&lt;Data&gt;,这意味着它已完全加载到内存中。
因为我只使用两个属性,所以我不需要检索整个对象来创建TimeTupels。

现在我的问题是如何实现性能提升?删除ToList 是正确的方法吗?

我尝试过的事情

  • 仅使用IQueryable&lt;Data&gt; 创建TimeTupel
    IQueryable&lt;Data&gt; actualData = onePerHour; 产生运行时错误(“System.InvalidOperationException: 'Null TypeMapping in Sql Tree'”)

    李>
  • 使用AsEnumerable:
    IEnumerable&lt;Data&gt; actualData = onePerHour.AsEnumerable(); 很慢,10 天的数据大约需要 22 秒

  • 使用ToList,如上面的代码所示(ToArray 几乎相等):
    List&lt;Data&gt; actualData = onePerHour.ToList(); 更快,相同数量的数据大约需要 5 秒

    李>

【问题讨论】:

    标签: c# performance linq entity-framework-core iqueryable


    【解决方案1】:

    在实际从 IQueryable 加载对象之前,您只能选择所需的属性。在您的 Where 语句之后使用 Select 仅加载您需要的内容。

    一个例子:

    假设你有一个如下所示的类:

    public class Person {
            public string Name { get; set; }
            public int Age { get; set; }
        }
    

    我可以初始化一个测试项目列表:

    var people = new List<Person> { new Person { Name = "John", Age = 10 }, new Person { Name = "Archie", Age = 40 } };
    

    然后我们应用过滤器:

    var filterred = people.Where(p => p.Age > 15).Select(p => p.Name).ToList();
    

    如果我想用选择创建一个新对象,要选择多个属性,我可以这样做:

    var objFilterred = people.Where(p => p.Age > 15).Select(p => new { FullName = p.Name  }).ToList();
    

    您不必使用匿名对象,也可以创建一个只包含您需要的属性的新类,然后简单地填充它。

    您不能“删除”ToList,因为这是实际执行查询的内容。 IQueryable 不是数据,它是一个尚未运行的查询,您可以链接任意数量的内容。最后一步是执行它,运行 ToList 之类的东西,以实际加载对象。只要您构建了 IQueryable 并在完成后执行它,那么您应该会看到执行速度有所提高

    【讨论】:

    • IEnumerable 也是如此,对吧?这只是关于如何获取条目的描述,但内存中没有实际数据。而且由于List.AddRange 采用IEnumerable (顺便说一句,IQueryable 也实现了)我认为没有必要在将数据添加到列表之前将其加载到内存中,对吧? AddRange 应该执行查询并检索它应该添加的数据,那么为什么我不能删除 ToList
    • 我使用了一个列表,只是为了更容易理解。所以只需在你的 where 之后添加一个 select 语句,然后在最后执行 ToList,它应该有助于解决性能问题
    【解决方案2】:

    您可以在 Select 语句中使用匿名类型来仅将所需的数据列检索到内存中,然后将内存中的数据从那里转换为 TimeTupel 类。它看起来像这样:

    var actualData = dataLastWeek
        .Where(d => 
            !dataLastWeek
            .Any(d2 =>
                d2.ArchiveTime.Date == d.ArchiveTime.Date &&
                d2.ArchiveTime.Hour == d.ArchiveTime.Hour &&
                d2.ArchiveTime < d.ArchiveTime))
        .Select(d => new { d.ArchiveTime, d.TempC, d.CO2Percent})
        .ToList();
    
    var tempCTupels = actualData.Select(d => new TimeTupel<float>(d.ArchiveTime, d.TempC)).ToList();
    
    var co2Tupels = actualData.Select(d => new TimeTupel<float>(d.ArchiveTime, d.CO2Percent)).ToList();
    

    【讨论】:

    • 可悲的是,只有大约 4% 的差异:/ 有没有办法删除 ToList (让它更快)?为什么它需要在内存中?
    • “内存中”是指将数据从数据库服务器中取出,然后放到 Web 服务器中。 Web 服务器负责将该数据转换为客户端可以理解的形式。限制检索的数据是一种很好的做法并且很有帮助,但我猜真正的问题在于如何索引和检索数据库服务器中的数据。您需要在数据库级别优化查询。
    • 网络服务器?这与网络服务器有什么关系?这应该在没有网络服务器的情况下适用。好吧,即使它似乎并没有太大改变性能,我也会这样做。谢谢:)
    • 很抱歉假设您正在处理基于 Web 的项目 - 无论代码在何处执行,相同的原则都将适用。
    猜你喜欢
    • 1970-01-01
    • 2012-11-17
    • 2018-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-21
    • 2021-04-14
    相关资源
    最近更新 更多