WCF RIA服务查询缓慢。

3
我有一个由Entity Framework支持的WCF RIA服务域服务,实体模型相当复杂。一般来说,大多数操作的性能都还可以。但是,我的Silverlight客户端包含一个主/细节视图。主视图是实体列表。在实体之间点击以选择它们会在服务器上触发方法,这平均需要5秒钟的时间 - 这是不可接受的。我使用Fiddler查看了通过网络进行的调用,发现所有时间都花费在了服务器上。
ServerGotRequest:   15:59:44.545
ServerBeginResponse:15:59:48.836
ServerDoneResponse: 15:59:48.836
ClientBeginResponse:15:59:48.836
ClientDoneResponse: 15:59:48.836
Overall Elapsed:    00:00:04.2940000

数据的返回量很小,只有大约6kb。我的问题在服务器端。虽然EF查询比较简单,但是我们有25个“Include”语句用于关联实体查询,其中包括4层嵌套(例如,“Include('1.2.3.4')”)。我认为瓶颈在于服务器上。并且,尽管生成的SQL很糟糕,但速度还是足够快的。执行SQL Profiler后发现数据返回量不算太大,只有3行左右,每行包含275列。我的问题在哪里?如何进行调试?如果在IQueryable GetEntity()方法的末尾设置断点,会发现在退出该方法后,在SQL Profiler中出现实际查询需要花费高达3秒钟的时间,这是怎么回事呢?请注意,我已经预先生成了我的Entity Framework视图,并且尝试使用编译查询来排除EF的“预热”时间,但没有任何改善。感谢您提供任何有关此问题的帮助。谢谢。

你有没有找到这个问题的可接受答案?我发现每次调用RIA服务都会有大约0.8秒的开销,即使服务只返回DTO而不访问数据库或执行其他可能很慢的操作。 - Josh Mouch
@Josh 看看我的答案,你需要找出延迟在哪里;在 Fiddler 中对网络调用进行分析,在 SQL Profiler 中对数据库调用进行分析,看看是客户端、网络还是服务器慢。 - TheNextman
谢谢。我通过在代码的各个地方插入 ASP.Net 服务器跟踪,找到了我开销过大的原因。结果发现是由 Unity 库内部抛出的大量异常所引起的。除了其作者之外,大多数人都认为这是 Unity 的一个漏洞。 - Josh Mouch
4个回答

3
我明白回答中告诉我要批量处理数据,不要加载整个实体等等;总的来说,我同意这种方法。然而,在这种情况下,我们正在选择单个实体 - 因为它来自规范化的数据库,需要跨多个表进行连接。
在这个例子中,EF生成的可怕查询仍然在0.2秒内执行。如果我想手写查询,可能会再快1/10。就目前而言,速度绰绰有余。整个实体在网络上的大小小于6Kb; 在我看来,这已经足够小了。
因此,在这个例子中,为了将单个实体分批处理成多个请求是行不通的。显然,如果我使用纯粹的 ADO.NET 和 Web Services/WCF,我就不会遇到这个问题。不管怎样,接下来就是答案:
我已经说明了编译 EF 查询并没有帮助。但是,似乎这是因为 RIA Services 在我的原始查询上应用了“Where EntityID = ID”的条件,并破坏了我的编译查询。
如果我只是在我的域服务中这样做:
(from e in ctx.Entities
.Include("SubEntity")
.Include("SubEntity.SubEntity")
// and so on, X20...
where e.EntityID == id
select e).FirstOrDefault();

这行代码几乎占用了全部执行时间,但是 SQL 很快。只是连接到 SQL Server 的速度很慢。所以这让我认为 EF 花费了很长时间来生成查询。

为了解决这个问题,我创建了一个预编译查询,直接通过 ID 选择实体,并将其材料化而不是返回 IQueryable:

 private static Func<DataContext, Guid, Entity> getEntityByEntityID =
        CompiledQuery.Compile<DataContext, Guid, Entity>(
        (ctx, id) => (from e in ctx.Entities
                        .Include("SubEntity")
                        .Include("SubEntity.SubEntity")
                        // and so on, X20...
                      where e.EntityID == id
                      select e).FirstOrDefault());

然后我在我的域服务中公开了一个新的操作,以使用编译的查询:

public Entity GetEntityByEntityID(Guid entityID)
    {
        return getEntityByEntityID(this.ObjectContext, entityID);
    }

结果:第一次调用时现在有点慢。对于后续的调用,整个服务调用的操作现在平均约为0.5秒。


1

对于这样的东西,我认为你通常最好不要使用Includes。用单个调用在Silverlight中加载主实体,然后根据需要加载其他包含的项目。

即使您最终需要在视图中使用它们所有,通过以较小的块拉取它们并在获取它们时更新视图,您可能会获得更好的性能。这肯定会比等待查询更好地提供用户体验。


是的,这可能会成为解决方案。我只希望我知道除了“性能不佳”之外还发生了什么。 - TheNextman
1
Julie Lerman警告服务器端过度使用Include,这也是我学到的教训。我相信您知道EF发出的SQL将数据拉伸成一行长数据。然后客户端的内部逻辑必须运行并将数据行的各个部分映射回不同的表/实体。从返回数据给客户端的角度来看,这不是理想的方法。MARS(多个活动记录集)更适合处理此问题,并且已经为存储过程实施了这一点,以克服在客户机/服务器日中的此类问题。 - codeputer
这不仅仅是“性能不佳”,而是“你没有做对”。开个玩笑。正常的应用程序不会将所有数据请求批量处理成单个调用,因此仅仅因为你可以这样做并不意味着你应该这样做。不要包含所有内容。只在需要时加载它们。这是所有Web开发中的做法,不仅仅是Silverlight。 - Bryant

0
你所说的问题,是关于图形序列化/反序列化的。此外,我们遇到了一些奇怪的问题,例如在Analyzer中执行SQL时速度很快,但从EF中执行时却很慢。尝试重建表格上的统计数据。

是的,感觉像是RIA服务中的序列化/反序列化问题或EF中的查询/对象材料化问题。但我无法在那个级别上进行分析或调试。这绝对不是SQL的问题。我的所有时间都花在甚至还没有到达数据库之前的代码上。 - TheNextman

0
在WCF RIA服务中,您必须记住,在发出回调之前将先加载客户端模型。这个客户端模型不是EF,而只是在服务器端上看起来像EF的一个呈现。WCF RIA服务调用会首先加载这个模型,然后才会回调并引用潜在模型中的实体。
我建议您查看Rx框架,以利用多个异步调用,然后再向UI客户端返回“已完成”的工作状态。使用Rx,您可以调用几个异步方法,并等待所有方法都完成后再返回到UI。每个调用将会分解客户端模型,让工作能够并行完成,以便在返回之前更有效地处理。请记住,这是一个SOA体系结构,很容易(针对自己)陷入阻塞调用的设计。
(任务并行库(TPL)也是另一种方式,但它正在进入SL5。我不太熟悉TPL和何时使用RX和/或TPL之间的细微差别。)

我使用这种策略来从分层树中拉取数据,我返回的是什么数据-首先是上层,然后是下层等。请记住,客户端上的导航属性只是覆盖下的linq查询,其中主键用于过滤外键。客户端模型(此时它不是EF模型,但接近)还具有引用完整性约束,但这些约束仅适用于插入记录时,而不是加载记录。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接