简体   繁体   English

第一个查询很慢,并且未生成预先生成的视图(可能)

[英]First query is slow and pre-generated views aren't being hit (probably)

I'm having a bit of trouble with the time it takes EF to pull some entities. 我在使用EF拉动某些实体的时间方面遇到了一些麻烦。 The entity in question has a boatload of props that live in 1 table, but it also has a handful of ICollection's that relate to other tables. 有问题的实体有一大堆道具存在于一张桌子中,但它也有一些与其他桌子相关的ICollection。 I've abandoned the idea of loading the entire object graph as it's way too much data and instead will have my Silverlight client send out a new request to my WCF service as details are needed. 我已经放弃了加载整个对象图的想法,因为它太多了数据,而是让我的Silverlight客户端向我的WCF服务发送新请求,因为需要详细信息。

After slimming down to 1 table's worth of stuff, it's taking roughly 8 seconds to pull the data, then another 1 second to .ToList() it up (I expect this to be < 1 second). 在减少到1个表的价值之后,需要大约8秒才能获取数据,然后又需要1秒钟.ToList()它(我预计这将是<1秒)。 I'm using the stopwatch class to take measurements. 我正在使用秒表类进行测量。 When I run the SQL query in SQL management studio, it takes only a fraction of a second so I'm pretty sure the SQL statement itself isn't the problem. 当我在SQL管理工作室中运行SQL查询时,它只需要几分之一秒,所以我很确定SQL语句本身不是问题。

Here is how I am trying to query my data: 以下是我尝试查询数据的方法:

public List<ComputerEntity> FindClientHardware(string client)
{
        long time1 = 0;
        long time2 = 0;
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();

        // query construction always takes about 8 seconds, give or a take a few ms.
        var entities =
            DbSet.Where(x => x.CompanyEntity.Name == client); // .AsNoTracking() has no impact on performance
        //.Include(x => x.CompanyEntity)
        //.Include(x => x.NetworkAdapterEntities) // <-- using these 4 includes has no impact on SQL performance, but faster to make lists without these
        //.Include(x => x.PrinterEntities)        // I've also abandoned the idea of using these as I don't want the entire object graph (although it would be nice)
        //.Include(x => x.WSUSSoftwareEntities)

        //var entities = Find(x => x.CompanyEntity.Name == client); // <-- another test, no impact on performance, same execution time

        stopwatch.Stop();
        time1 = stopwatch.ElapsedMilliseconds;
        stopwatch.Restart();

        var listify = entities.ToList(); // 1 second with the 1 table, over 5 seconds if I use all the includes. 

        stopwatch.Stop();
        time2 = stopwatch.ElapsedMilliseconds;

        var showmethesql = entities.ToString();

        return listify;
    }

I'm assuming that using the .Include means eager loading, although it isn't relevant in my current case as I just want the 1 table's worth of stuff. 我假设使用.Include意味着急切加载,虽然它与我目前的情况无关,因为我只想要1个表的价值。 The SQL generated by this statement (which executes super fast in SSMS) is: 此语句生成的SQL(在SSMS中执行超快)是:

SELECT 
  [Extent1].[AssetID] AS [AssetID],  
  [Extent1].[ClientID] AS [ClientID],  
  [Extent1].[Hostname] AS [Hostname],  
  [Extent1].[ServiceTag] AS [ServiceTag],  
  [Extent1].[Manufacturer] AS [Manufacturer],  
  [Extent1].[Model] AS [Model],  
  [Extent1].[OperatingSystem] AS [OperatingSystem],  
  [Extent1].[OperatingSystemBits] AS [OperatingSystemBits],  
  [Extent1].[OperatingSystemServicePack] AS [OperatingSystemServicePack],  
  [Extent1].[CurrentUser] AS [CurrentUser],  
  [Extent1].[DomainRole] AS [DomainRole],  
  [Extent1].[Processor] AS [Processor],  
  [Extent1].[Memory] AS [Memory],  
  [Extent1].[Video] AS [Video],  
  [Extent1].[IsLaptop] AS [IsLaptop],  
  [Extent1].[SubnetMask] AS [SubnetMask],  
  [Extent1].[WINSserver] AS [WINSserver],  
  [Extent1].[MACaddress] AS [MACaddress],  
  [Extent1].[DNSservers] AS [DNSservers],  
  [Extent1].[FirstSeen] AS [FirstSeen],  
  [Extent1].[IPv4] AS [IPv4],  
  [Extent1].[IPv6] AS [IPv6],  
  [Extent1].[PrimaryUser] AS [PrimaryUser],  
  [Extent1].[Domain] AS [Domain],  
  [Extent1].[CheckinTime] AS [CheckinTime],  
  [Extent1].[ActiveComputer] AS [ActiveComputer],  
  [Extent1].[NetworkAdapterDescription] AS [NetworkAdapterDescription],  
  [Extent1].[DHCP] AS [DHCP] 
FROM  
  [dbo].[Inventory_Base] AS [Extent1] 
  INNER JOIN [dbo].[Entity_Company] AS [Extent2] 
    ON [Extent1].[ClientID] = [Extent2].[ClientID] 
WHERE 
  [Extent2].[CompanyName] = @p__linq__0

Which is basically a select all columns in this table, join a second table that has a company name, and filter with a where clause of companyname == input value to the method. 这基本上是在此表中选择所有列,连接具有公司名称的第二个表,并使用companyname ==输入值的where子句过滤到该方法。 The particular company I'm pulling only returns 75 records. 我拉的特定公司只返回75条记录。

Disabling object tracking with .AsNoTracking() has zero impact on execution time. 使用.AsNoTracking()禁用对象跟踪对执行时间没有任何影响。

I also gave the Find method a go, and it had the exact same execution time. 我还给了Find方法,它具有完全相同的执行时间。 The next thing I tried was to pregenerate the views in case the issue was there. 我尝试的下一件事是在出现问题时预先生成视图。 I am using code first, so I used the EF power tools to do this. 我首先使用代码,所以我使用EF电动工具来做到这一点。

This long period of time to run this query causes too long of a delay for my users. 运行此查询的这段很长时间会导致用户延迟太长时间。 When I hand write the SQL code and don't touch EF, it is super quick. 当我手写SQL代码并且不接触EF时,它非常快。 Any ideas as to what I'm missing? 关于我缺少什么的任何想法?

Also, maybe related or not, but since I'm doing this in WCF which is stateless I assume absolutely nothing gets cached? 也许,可能相关或不相关,但因为我在WCF这是无状态的,我假设绝对没有任何缓存? The way I think about it is that every new call is a firing up this WCF service library for the first time, therefore there is no pre-existing cache. 我想到的方式是每次新调用都是第一次触发此WCF服务库,因此没有预先存在的缓存。 Is this an accurate assumption? 这是一个准确的假设吗?

Update 1 更新1
So I ran this query twice within the same unit test to check out the cold/warm query thing. 所以我在同一个单元测试中运行了两次这个查询来检查冷/热查询的事情。 The first query is horrible as expected, but the 2nd one is lightning fast clocking in at 350ms for the whole thing. 第一个问题是预期的可怕,但第二个问题是整个事情在350毫秒闪电般快速计时。 Since WCF is stateless, is every single call to my WCF service going to be treated as this first ugly-slow query? 既然WCF是无状态的,那么对我的WCF服务的每一次调用都会被视为第一个丑陋慢的查询吗? Still need to figure out how to get this first query to not suck. 仍然需要弄清楚如何让第一个查询不要吮吸。
Update 2 更新2
You know those pre-generated views I mentioned earlier? 您知道我之前提到的那些预先生成的视图吗? Well... I don't think they are being hit. 嗯......我不认为他们受到了打击。 I put a few breakpoints in the autogenerated-by-EF-powertools ReportingDbContext.Views.cs file, and they never get hit. 我在自动生成的EF-powertools ReportingDbContext.Views.cs文件中放了几个断点,它们永远不会受到攻击。 This coupled with the cold/warm query performance I see, this sounds like it could be meaningful. 再加上我看到的冷/热查询性能,这听起来有点意义。 Is there a particular way I need to pregenerate views with the EF power tools in a code first environment? 我是否需要在代码优先环境中使用EF电源工具预生成视图?

Got it! 得到它了! The core problem was the whole cold query thing. 核心问题是整个冷查询的事情。 How to get around this cold query issue? 如何解决这个冷查询问题? By making a query. 通过进行查询。 This will "warm up" EntityFramework so that subsequent query compilation is much faster. 这将“预热”EntityFramework,以便后续查询编译更快。 My pre-generated views did nothing to help with the query I was compiling in this question, but they do seem to work if I want to dump an entire table to an array (a bad thing). 我预先生成的视图对我在这个问题中编译的查询没有任何帮助,但是如果我想将整个表转储到数组(这是一件坏事),它们似乎确实有效。 Since I am using WCF which is stateless, will I have to "warm up" EF for every single call? 由于我使用无状态的WCF,我是否必须为每次通话“预热”EF? Nope! 不! Since EF lives in the app domain and not the context, I just to need to do my warm up on the init of the service. 由于EF存在于app域而不是上下文中,我只需要对init的服务进行热身。 For dev purposes I self host, but in production it lives in IIS. 出于开发目的,我自己主持,但在生产中它存在于IIS中。

To do the query warm up, I made a service behavior that takes care of this for me. 为了使查询热身,我做了一个服务行为,为我处理这个问题。 Create your behavior class as such: 像这样创建行为类:

using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels; // for those without resharper, here are the "usings"
using System.ServiceModel.Description;

public class InitializationBehavior : Attribute, IServiceBehavior 
{
    public InitializationBehavior()
    {

    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {

    }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
                                     BindingParameterCollection bindingParameters)
    {
        Bootstrapper.WarmUpEF();
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {

    }
}

I then used this to do the warmup: 然后我用它来做热身:

  public static class Bootstrapper
  {
    public static int initialized = 0;

    public static void WarmUpEF()
    {            
        using (var context = new ReportingDbContext())
        {
            context.Database.Initialize(false);
        }
        initialized = 9999; // I'll explain this
    }
}

This SO question helped with the warmup code: How do I initialize my Entity Framework queries to speed them up? 这个SO问题有助于预热代码: 如何初始化我的实体框架查询以加快它们的速度?

You then slap this behavior on your WCF service like so: 然后你在你的WCF服务上打了这个行为,如下所示:

   [InitializationBehavior]
   public class InventoryService : IInventoryService 
   {
     // implement your service
   }

I launched my services project in debug mode which in turn fired up the initialization behavior. 我在调试模式下启动了我的服务项目,这又启动了初始化行为。 After spamming the method that makes the query referenced in my question, my breakpoint in the behavior wasn't being hit (other than being hit when I first self hosted it). 在我的问题中垃圾邮件引发查询的方法之后,行为中的断点没有被击中(除了在我第一次自己托管它时被击中)。 I verified that it was it by checking out the static initialized variable. 我通过检查静态初始化变量验证了它。 I then published this bad boy into IIS with my verification int and it had the exact same behavior. 然后我用我的验证int将这个坏男孩发布到IIS中,它具有完全相同的行为。

So, in short, if you are using Entity Framework 5 with a WCF service and don't want a crappy first query, warm it up with a service behavior. 因此,简而言之,如果您正在使用带有WCF服务的Entity Framework 5并且不想要一个糟糕的第一个查询,请使用服务行为进行预热。 There are probably other/better ways of doing this, but this way works too! 可能有其他/更好的方法,但这种方式也有效!

edit: 编辑:
If you are using NUnit and want to warm up EF for your unit tests, setup your test as such: 如果您正在使用NUnit并希望为单元测试预热EF,请按以下方式设置测试:

[TestFixture]
public class InventoryTests
{

    [SetUp]
    public void Init()
    {
        // warm up EF.
        using (var context = new ReportingDbContext())
        {
            context.Database.Initialize(false);
        }
        // init other stuff
    }

  // tests go here
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM