4

我在 EF 拉一些实体时遇到了一些麻烦。有问题的实体有一大堆存在于 1 个表中的道具,但它也有一些与其他表相关的 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 个表的东西。此语句(在 SSMS 中执行速度超快)生成的 SQL 为:

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 电动工具预生成视图吗?

4

1 回答 1

4

知道了!核心问题是整个冷查询。如何解决这个冷查询问题?通过查询。这将“预热”EntityFramework,以便后续查询编译更快。我预先生成的视图对我在这个问题中编译的查询没有任何帮助,但如果我想将整个表转储到数组中(一件坏事),它们似乎确实有效。由于我使用的是无状态的 WCF,我是否必须为每次调用“热身”EF?没有!由于 EF 存在于应用程序域而不是上下文中,因此我只需要在服务的初始化时进行热身。出于开发目的,我自己托管,但在生产中它存在于 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 问题有助于热身代码: 如何初始化我的实体框架查询以加快它们的速度?

然后,您可以像这样在您的 WCF 服务上执行此行为:

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

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

因此,简而言之,如果您将 Entity Framework 5 与 WCF 服务一起使用,并且不希望第一次查询很糟糕,请使用服务行为对其进行预热。可能有其他/更好的方法可以做到这一点,但这种方法也有效!

编辑:
如果您使用 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
}
于 2013-09-19T23:01:19.367 回答