我在使用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电源工具预生成视图?
答案 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
}