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

时间:2013-09-19 18:53:16

标签: c# performance entity-framework entity-framework-5

我在使用EF拉动某些实体所花费的时间方面遇到了一些麻烦。有问题的实体有一大堆道具存在于一张桌子中,但它也有一些与其他桌子相关的ICollection。我已经放弃了加载整个对象图的想法,因为它太多了数据,而是让我的Silverlight客户端向我的WCF服务发送一个新请求,因为需要详细信息。

在减少到1个表的价值之后,大约花了8秒钟来拉动数据,然后又花了1秒钟.ToList()它(我预计这将是<1秒)。我正在使用秒表类进行测量。当我在SQL管理工作室中运行SQL查询时,它只需要几分之一秒,所以我很确定SQL语句本身不是问题。

以下是我尝试查询数据的方式:

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;
    }

我假设使用.Include意味着急切加载,虽然它与我目前的情况无关,因为我只想要1个表的价值。此语句生成的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

这基本上是在此表中选择所有列,连接具有公司名称的第二个表,并使用companyname ==输入值的where子句过滤到该方法。我拉的特定公司只返回75条记录。

使用.AsNoTracking()禁用对象跟踪对执行时间没有任何影响。

我还给了Find方法,它具有完全相同的执行时间。我尝试的下一件事是在出现问题时预先生成视图。我首先使用代码,因此我使用EF电动工具来完成此操作。

运行此查询的这段时间会导致我的用户延迟太长时间。当我手写SQL代码并且不接触EF时,它非常快。关于我缺少的任何想法?

此外,也许相关或不相关,但因为我在WCF这是无状态的,我假设绝对没有任何缓存?我想到的方式是每次新调用都是第一次触发此WCF服务库,因此没有预先存在的缓存。这是一个准确的假设吗?

更新1
所以我在同一个单元测试中运行了两次这个查询来检查冷/热查询的事情。第一个问题是预期的可怕,但第二个问题是整个事情在350毫秒闪电般快速计时。既然WCF是无状态的,那么对我的WCF服务的每一次调用都会被视为第一个丑陋慢的查询吗?仍然需要弄清楚如何让第一个查询不要吮吸。
更新2
您知道我之前提到的那些预先生成的视图吗?嗯......我不认为他们受到了打击。我在自动生成的EF-powertools ReportingDbContext.Views.cs文件中放了几个断点,它们永远不会受到攻击。再加上我看到的冷/热查询性能,这听起来有点意义。我是否需要在代码优先环境中使用EF电源工具预生成视图?

1 个答案:

答案 0 :(得分:4)

知道了!核心问题是整个冷查询的事情。如何解决这个冷查询问题?通过进行查询。这将“预热”EntityFramework,以便后续查询编译更快。我预先生成的视图对我在这个问题中编译的查询没有任何帮助,但是如果我想将整个表转储到数组(这是一件坏事),它们似乎确实有效。由于我使用无状态的WCF,我是否必须为每次通话“预热”EF?不!由于EF存在于app域而不是上下文中,我只需要对init的服务进行热身。出于开发目的,我自己主持,但在生产中它存在于IIS中。

为了使查询热身,我做了一个服务行为,为我处理这个问题。像这样创建行为类:

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)
    {

    }
}

然后我用它做了预热:

  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
    }
}

这个SO问题有助于预热代码: How do I initialize my Entity Framework queries to speed them up?

然后您在WCF服务上发出此行为,如下所示:

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

我在调试模式下启动了我的服务项目,这又启动了初始化行为。在我的问题中垃圾邮件引发查询的方法之后,行为中的断点没有被击中(除了在我第一次自己托管它时被击中)。我通过检查静态初始化变量验证了它。然后我用我的验证int将这个坏男孩发布到IIS中,它具有完全相同的行为。

因此,简而言之,如果您正在使用带有WCF服务的Entity Framework 5并且不想要一个糟糕的第一个查询,请使用服务行为加热它。可能有其他/更好的方法,但这种方式也有效!

编辑:
如果您正在使用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
}