【问题标题】:Why does a WCF service execute query all before persisting a new entity?为什么 WCF 服务在持久化新实体之前执行查询全部?
【发布时间】:2012-07-30 02:31:14
【问题描述】:

我正在开发一个更大的项目,它使用 WCF 数据服务和 OData 在前端和后端之间进行通信,我们在创建实体时遇到了性能问题。深入挖掘问题后发现,当客户端尝试保存新实体时,会在服务中执行查询,返回所有实体,然后将其丢弃,然后插入数据。

我认为问题与我们的应用程序的创建方式有关。它使用自定义提供程序,但奇怪的是即使是最简单的测试项目也会出现问题。

我使用了下面发布的以下代码,并在 Provider.cs 中设置了断点。当我使用 HttpRequester Firefox 插件调用它以根据 OData 发送 POST 请求(插入)时

  1. IQueryable 被调用
  2. CreateResource 被调用
  3. SetValue 被多次调用
  4. SaveChanges 被调用

我有一个问题,为什么调用 IQueryable 以及如何防止它?我无法弄清楚。

在我们的现实生活场景中,而不是在这个测试应用程序中,IQueryable 返回数千条记录,甚至更多,并且可能很耗时。这会影响插入操作的性能。

请求插入记录

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xml:base="http://localhost:50366/MyDataService.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
<id>http://localhost:50366/MyDataService.svc/Employees(0)</id>
<title type="text"></title>
<updated>2012-07-31T18:03:45Z</updated>
<author>
  <name />
</author>
<link rel="edit" title="Employee" href="Employees(0)" />
<category term="Test.Dto.Employee" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
  <m:properties>
    <d:EmployeeID m:type="Edm.Int32">2</d:EmployeeID>
    <d:LastName>Test</d:LastName>
    <d:FirstName>Data</d:FirstName>
  </m:properties>
</content>
</entry>

MyDataService.svc.cs:

using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;

namespace Test
{
    public class MyDataService : DataService<Provider>
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.All);
            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }
}

Employee.cs

using System.Data.Services.Common;

namespace Test.Dto
{

    [DataServiceKey("EmployeeID")]
    public class Employee
    {
        public virtual int EmployeeID { set; get; }
        public virtual string LastName { set; get; }
        public virtual string FirstName { set; get; }
    }

}

Provider.cs

using System.Linq;
using Test.Dto;
using System.Collections.Generic;
using System.Data.Services;
namespace Test
{

    public class Provider : IUpdatable
    {
        static IList<Employee> _employees = new List<Employee>() {
            new Employee {
                EmployeeID = 1,
                FirstName = "No",
                LastName = "Name"
            }
        };

        IList<Employee> _updates = new List<Employee>();

        public IQueryable<Employee> Employees 
        { 
            get 
            {
                return Provider._employees.AsQueryable();
            } 
        }

        public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded)
        {
            throw new System.NotImplementedException();
        }

        public void ClearChanges()
        {
            _updates.Clear();
        }

        public object CreateResource(string containerName, string fullTypeName)
        {
            if (Equals(fullTypeName, typeof(Employee).FullName))
            {
                var entity = new Employee();
                _updates.Add(entity);
                return entity;
            }
            else
            {
                throw new System.NotImplementedException();
            }                              
        }

        public void DeleteResource(object targetResource)
        {
            throw new System.NotImplementedException();
        }

        public object GetResource(IQueryable query, string fullTypeName)
        {
            throw new System.NotImplementedException();
        }

        public object GetValue(object targetResource, string propertyName)
        {
            throw new System.NotImplementedException();
        }

        public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved)
        {
            throw new System.NotImplementedException();
        }

        public object ResetResource(object resource)
        {
            throw new System.NotImplementedException();
        }

        public object ResolveResource(object resource)
        {
            return resource;
        }

        public void SaveChanges()
        {
            foreach (var item in _updates)
            {
                _employees.Add(item);                
            }
        }

        public void SetReference(object targetResource, string propertyName, object propertyValue)
        {
            throw new System.NotImplementedException();
        }

        public void SetValue(object targetResource, string propertyName, object propertyValue)
        {
            targetResource.GetType().GetProperty(propertyName).SetValue(targetResource, propertyValue, null);
        }
    }

}

中断 IQueryable 时的堆栈跟踪

>    WebApplication5.DLL!Test.Provider.Employees.get() Line 24    C#
    [Lightweight Function]    
    System.Data.Services.dll!System.Data.Services.Providers.ReflectionServiceProvider.GetResourceContainerInstance(System.Data.Services.Providers.ResourceSet resourceContainer) + 0x1ec bytes    
    System.Data.Services.dll!System.Data.Services.Providers.BaseServiceProvider.GetQueryRootForResourceSet(System.Data.Services.Providers.ResourceSet container) + 0xb bytes    
    System.Data.Services.dll!System.Data.Services.RequestUriProcessor.CreateFirstSegment(System.Data.Services.IDataService service, string identifier, bool checkRights, string queryPortion, bool isLastSegment, out bool crossReferencingUrl) + 0x40e bytes    
    System.Data.Services.dll!System.Data.Services.RequestUriProcessor.CreateSegments(string[] segments, System.Data.Services.IDataService service) + 0x103 bytes    
    System.Data.Services.dll!System.Data.Services.RequestUriProcessor.ProcessRequestUri(System.Uri absoluteRequestUri, System.Data.Services.IDataService service) + 0x3b bytes    
    System.Data.Services.dll!System.Data.Services.DataService<Test.Provider>.ProcessIncomingRequestUri() + 0xe2 bytes    
    System.Data.Services.dll!System.Data.Services.DataService<Test.Provider>.HandleRequest() + 0xc0 bytes    
    System.Data.Services.dll!System.Data.Services.DataService<Test.Provider>.ProcessRequestForMessage(System.IO.Stream messageBody) + 0x65 bytes    
    [Lightweight Function]    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(object instance, object[] inputs, out object[] outputs) + 0x33f bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x137 bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x5e bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x6c bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x89 bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x59 bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x3b bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x4e bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x125 bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(ref System.ServiceModel.Dispatcher.MessageRpc rpc) + 0x34 bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.MessageRpc.Process(bool isOperationContextSet) + 0xff bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(System.ServiceModel.Channels.RequestContext request, bool cleanThread, System.ServiceModel.OperationContext currentOperationContext) + 0x44b bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(System.ServiceModel.Channels.RequestContext request, System.ServiceModel.OperationContext currentOperationContext) + 0x127 bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(System.IAsyncResult result) + 0x43 bytes    
    System.ServiceModel.dll!System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(System.IAsyncResult result) + 0x44 bytes    
    System.Runtime.DurableInstancing.dll!System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(System.IAsyncResult result) + 0x32 bytes    
    System.Runtime.DurableInstancing.dll!System.Runtime.AsyncResult.Complete(bool completedSynchronously) + 0xfd bytes    
    System.Runtime.DurableInstancing.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.RequestContext>.AsyncQueueReader.Set(System.Runtime.InputQueue<System.ServiceModel.Channels.RequestContext>.Item item) + 0x44 bytes    
    System.Runtime.DurableInstancing.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.RequestContext>.EnqueueAndDispatch(System.Runtime.InputQueue<System.ServiceModel.Channels.RequestContext>.Item item, bool canDispatchOnThisThread) + 0x1aa bytes    
    System.Runtime.DurableInstancing.dll!System.Runtime.InputQueue<System.ServiceModel.Channels.RequestContext>.EnqueueAndDispatch(System.ServiceModel.Channels.RequestContext item, System.Action dequeuedCallback, bool canDispatchOnThisThread) + 0x5e bytes    
    System.ServiceModel.dll!System.ServiceModel.Channels.SingletonChannelAcceptor<System.ServiceModel.Channels.IReplyChannel,System.ServiceModel.Channels.ReplyChannel,System.ServiceModel.Channels.RequestContext>.Enqueue(System.ServiceModel.Channels.RequestContext item, System.Action dequeuedCallback, bool canDispatchOnThisThread) + 0x6b bytes    
    System.ServiceModel.dll!System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(System.ServiceModel.Channels.HttpRequestContext context, System.Action callback) + 0x1b4 bytes    
    System.ServiceModel.Activation.dll!System.ServiceModel.Activation.HostedHttpTransportManager.HttpContextReceived(System.ServiceModel.Activation.HostedHttpRequestAsyncResult result) + 0xd6 bytes    
    System.ServiceModel.Activation.dll!System.ServiceModel.Activation.HostedHttpRequestAsyncResult.HandleRequest() + 0x232 bytes    
    System.ServiceModel.Activation.dll!System.ServiceModel.Activation.HostedHttpRequestAsyncResult.BeginRequest() + 0x27 bytes    
    System.ServiceModel.Activation.dll!System.ServiceModel.Activation.HostedHttpRequestAsyncResult.OnBeginRequest(object state) + 0x49 bytes    
    System.ServiceModel.Activation.dll!System.ServiceModel.AspNetPartialTrustHelpers.PartialTrustInvoke(System.Threading.ContextCallback callback, object state) + 0x35 bytes    
    System.ServiceModel.Activation.dll!System.ServiceModel.Activation.HostedHttpRequestAsyncResult.OnBeginRequestWithFlow(object state) + 0x7a bytes    
    System.Runtime.DurableInstancing.dll!System.Runtime.IOThreadScheduler.ScheduledOverlapped.IOCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* nativeOverlapped) + 0x78 bytes    
    System.Runtime.DurableInstancing.dll!System.Runtime.Fx.IOCompletionThunk.UnhandledExceptionFrame(uint error, uint bytesRead, System.Threading.NativeOverlapped* nativeOverlapped) + 0x39 bytes    
    mscorlib.dll!System.Threading._IOCompletionCallback.PerformIOCompletionCallback(uint errorCode, uint numBytes, System.Threading.NativeOverlapped* pOVERLAP) + 0x74 bytes    
    [Native to Managed Transition]    
    [Appdomain Transition]    
    [Native to Managed Transition]    

【问题讨论】:

  • 我试图重现这一点,但无法访问可查询的内容。您能否在发生这种情况时设置断点并捕获调用堆栈?另外,您使用的是哪个版本的 WCF 数据服务?
  • 为什么要指定条目的id?不是只需要更新吗?还是那是响应的 xml?
  • @VitekKarasMSFT 我们有一个随 VS2010 SP1 一起安装的。 System.Data.Services 是 v4.0.30319。
  • @VitekKarasMSFT 我已将堆栈跟踪附加到原始帖子。
  • @JasonFreitas 发布的 XML 是请求 XML。条目 ID 在那里,因为我已经复制并编辑了一个查询并发布了它。我忘了删除id。我试图删除它,但它并没有改变行为。 IQueryable 仍然被调用。

标签: c# .net wcf-data-services odata


【解决方案1】:

这里有几个问题:

  1. 我们在 WCF DS 中有一个错误。当我们在 .NET Framework 中时,很难考虑修复这个问题,因为可能会发生重大更改。现在我们已经脱离了框架并且我们有一个公开的问题,我们应该解决这个问题。
  2. 我们不枚举 IQueryable,这意味着 QueryProvider 永远不会真正执行查询。只要 getter 只是返回一个 IQueryable 并且不做任何工作,我们应该不会看到任何超过名义上的性能损失。

综上所述,这是我们可以而且应该解决的问题,这样我们就不会让人们对此感到疑惑。如果我们有足够的带宽,我们会尝试在 5.0.2 或 5.1.0 中修复。

【讨论】:

  • 谢谢你的回答,已经很清楚了。在我们的例子中,getter 做了更多的工作,并且很难将其排除在外。有没有一种很好的方法可以在 getter 中解决这个问题?例如。确定请求是 POST 吗?
  • 我想你可能会喜欢 ChangeInterceptor,但我不建议这样做。当您说 getter 做了更多工作时,您的意思是它完成了提供 IQueryable 的工作,或者 IQueryable 的评估需要工作?如果是后者,你真的没有什么可担心的。如果是前者,请尝试使用 ChangeInterceptor 作为临时修复。
  • 重构你的 getter 和 IQueryable 应该很容易,这样 getter 只收集必要的信息,但不执行任何实际工作,并将所有内容存储在它返回的 IQueryable 中。并且 IQueryable 仅在执行(枚举)后才执行真正的工作。这就是 IQueryable 的工作方式,获取 IQueryable 的实例应该非常便宜,执行是昂贵的部分。
猜你喜欢
  • 2018-09-30
  • 2013-08-13
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多