2012-05-11 15 views
6

मेरे पास एक वेबएपी विधि है जो RavenDB दस्तावेज़ों का IQueryable देता है। कॉलर को संभव परिणामों की संख्या जानने की आवश्यकता है (क्योंकि वास्तविक परिणाम सीमित/पजे हुए हैं)। - तो आँकड़े खाली हो जाएगालौटने योग्य IQueryable लेकिन कुल Results शीर्षलेख डालने के लिए रैवेन आंकड़ों की आवश्यकता है

HttpContext.Current.Response.AddHeader("Total-Result-Count", 
    resultsStats.TotalResults.ToString()) 

दुर्भाग्य से, यह काम नहीं करेगा, क्योंकि IQueryable नहीं चढ़े, जिस वास्तव में अभी तक निष्पादित:

तो, मैं अपने WebAPI विधि के अंत में कुछ इस तरह की है।

मैं को क्वेरी प्रतिक्रिया के बाद तक आंकड़े प्रतिक्रिया-शीर्षलेख की आबादी को कैसे हटा सकता हूं?

[अद्यतन]

मैं परिणाम पर कब्जा करने के बाद नियंत्रक कार्रवाई निष्पादित था एक ActionFilter लागू करने का प्रयास किया है ... लेकिन यह पहले IQueryable वास्तव में प्रगणित है ActionFilter शुरू हो जाती है लगता है ...

public class CountQueryableResultsActionFilter : ActionFilterAttribute 
{ 
    public override void OnActionExecuted(HttpActionExecutedContext filterContext) 
    { 
     var controllerStats = filterContext.ActionContext.ControllerContext.Controller as IControllerStatistics; 
     System.Web.HttpContext.Current.Response.AddHeader("Total-Result-Count", controllerStats.TotalResults.ToString()); 
    } 
} 

आईएफ, मैंने वेबएपी विधि के अंत में "IQueryable.ToArray()" कहा, तो लिंक क्वेरी तुरंत निष्पादित हो जाती है, यह आंकड़े उत्पन्न करती है, और सबकुछ काम करता है - लेकिन इससे उपयोगकर्ता को अपना आवेदन करने में सक्षम होने से रोका जा सकता है अपने ओडाटा फिल्टर आदि ...

+0

आप के बजाय विधि मानकों के माध्यम से फिल्टर समर्थन विचार किया है? इस तरह से आप विधि रिटर्न से पहले परिणाम गिनती प्राप्त कर सकते हैं। – cecilphillip

+0

ज़रूर - लेकिन फिर मुझे उन सभी तरीकों की भविष्यवाणी करनी होगी जो सेवा उपभोक्ता परिणाम को बाधित करना चाहते हैं। इससे बचें ओडाटा का पूरा बिंदु था - क्षमा करें, नहीं, यह कोई विकल्प नहीं है। – Adam

+0

एकमात्र अन्य विकल्प जो मैं कल्पना कर सकता हूं कि आपके पास http हेडर वैल्यू – cecilphillip

उत्तर

3

ठीक है - मैंने इसे समझ लिया।

निम्नलिखित परिणामस्वरूप केवल एक ही रावेन क्वेरी जारी की जा रही है, जो परिणाम, और परिणाम-गणना दोनों देता है।

इस क्षेत्र में उनके प्रयोगों के लिए David Ruttka पर धन्यवाद। मैंने अपने कोड को RavenDb के साथ काम करने के लिए अनुकूलित किया है। यह कोड परिणाम लौटाएगा, और परिणाम-डेटाबेस डेटाबेस क्वेरी के माध्यम से गिनती होगी, जैसा कि RavenDB का इरादा था।

मैंने नीचे अपना कोड जोड़ दिया है - इसका उपयोग करने के लिए, आपको अपनी वेबएपी विधि से IRavenQueryable<T> वापस करना होगा (IQueryable<T> नहीं)। फिर, आपके Uri के लिए $ inlinecount = allpages जोड़ना हैंडलर का आह्वान करेगा। यह कोड अन्य ओडीटा क्वेरी एक्सटेंशन ($ ले, $ वगैरह आदि) तोड़ नहीं देगा

नोट: यह कोड 'इनलाइन' तकनीक का उपयोग करता है, जिसमें संदेश संदेश में आंकड़े वापस आते हैं - आप कोड बदल सकते हैं यदि आपको पसंद आया तो शीर्षलेख में आंकड़ों को इंजेक्ट करने के लिए - मैंने अभी मानक तरीके से जाना चुना है जो ओडाटा काम करता है।

आप रावण उत्पन्न करने वाले किसी भी और सभी आंकड़ों को शामिल करने के लिए इस कोड को अनुकूलित कर सकते हैं।

एएसपी.NET के साथ हैंडलर पंजीकृत करने के लिए निम्न कोड का उपयोग करें (अपने Global.asax में।सीएस)

RegistrationCode:

GlobalConfiguration.Configuration.MessageHandlers.Add(new WebApi.Extensions.InlineRavenCountHandler());

हैंडलर कोड:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Web; 
using System.Net.Http; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Reflection; 
using System.Net.Http.Headers; 
using System.Net; 

namespace WebApi.Extensions 
{ 
    public class InlineRavenCountHandler : DelegatingHandler 
    { 
     protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
     { 
      if (!ShouldInlineCount(request)) 
       return base.SendAsync(request, cancellationToken); 

      // Otherwise, we have a continuation to work our magic... 
      return base.SendAsync(request, cancellationToken).ContinueWith(
       t => 
       { 
        var response = t.Result; 

        // Is this a response we can work with? 
        if (!ResponseIsValid(response)) return response; 

        var pagedResultsValue = this.GetValueFromObjectContent(response.Content); 
        Type queriedType; 

        // Can we find the underlying type of the results? 
        if (pagedResultsValue is IQueryable) 
        { 
         queriedType = ((IQueryable)pagedResultsValue).ElementType; 

         // we need to work with an instance of IRavenQueryable to support statistics 
         var genericQueryableType = typeof(Raven.Client.Linq.IRavenQueryable<>).MakeGenericType(queriedType); 

         if (genericQueryableType.IsInstanceOfType(pagedResultsValue)) 
         { 
          Raven.Client.Linq.RavenQueryStatistics stats = null; 

          // register our statistics object with the Raven query provider. 
          // After the query executes, this object will contain the appropriate stats data 
          dynamic dynamicResults = pagedResultsValue; 
          dynamicResults.Statistics(out stats); 


          // Create the return object. 
          var resultsValueMethod = 
           this.GetType().GetMethod(
            "CreateResultValue", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(
             new[] { queriedType }); 

          // Create the result value with dynamic type 
          var resultValue = resultsValueMethod.Invoke(
           this, new[] { stats, pagedResultsValue }); 

          // Push the new content and return the response 
          response.Content = CreateObjectContent(
           resultValue, response.Content.Headers.ContentType); 
          return response; 

         } 
         else 
          return response; 
        } 
        else 
         return response; 
       }); 
     } 

     private bool ResponseIsValid(HttpResponseMessage response) 
     { 
      // Only do work if the response is OK 
      if (response == null || response.StatusCode != HttpStatusCode.OK) return false; 

      // Only do work if we are an ObjectContent 
      return response.Content is ObjectContent; 
     } 

     private bool ShouldInlineCount(HttpRequestMessage request) 
     { 
      var queryParams = request.RequestUri.ParseQueryString(); 
      var inlinecount = queryParams["$inlinecount"]; 
      return string.Compare(inlinecount, "allpages", true) == 0; 
     } 

    // Dynamically invoked for the T returned by the resulting ApiController 
    private ResultValue<T> CreateResultValue<T>(Raven.Client.Linq.RavenQueryStatistics stats, IQueryable<T> pagedResults) 
    { 
     var genericType = typeof(ResultValue<>); 
     var constructedType = genericType.MakeGenericType(new[] { typeof(T) }); 

     var ctor = constructedType 
      .GetConstructors().First(); 

     var instance = ctor.Invoke(null); 

     var resultsProperty = constructedType.GetProperty("Results"); 
     resultsProperty.SetValue(instance, pagedResults.ToArray(), null); 

     var countProperty = constructedType.GetProperty("Count"); 
     countProperty.SetValue(instance, stats.TotalResults, null); 

     return instance as ResultValue<T>; 
    } 

     // We need this because ObjectContent's Value property is internal 
     private object GetValueFromObjectContent(HttpContent content) 
     { 
      if (!(content is ObjectContent)) return null; 

      var valueProperty = typeof(ObjectContent).GetProperty("Value", BindingFlags.Instance | BindingFlags.NonPublic); 
      if (valueProperty == null) return null; 

      return valueProperty.GetValue(content, null); 
     } 

     // We need this because ObjectContent's constructors are internal 
     private ObjectContent CreateObjectContent(object value, MediaTypeHeaderValue mthv) 
     { 
      if (value == null) return null; 

      var ctor = typeof(ObjectContent).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(
       ci => 
       { 
        var parameters = ci.GetParameters(); 
        if (parameters.Length != 3) return false; 
        if (parameters[0].ParameterType != typeof(Type)) return false; 
        if (parameters[1].ParameterType != typeof(object)) return false; 
        if (parameters[2].ParameterType != typeof(MediaTypeHeaderValue)) return false; 
        return true; 
       }); 

      if (ctor == null) return null; 

      return ctor.Invoke(new[] { value.GetType(), value, mthv }) as ObjectContent; 
     } 
    } 

    public class ResultValue<T> 
    { 
     public int Count { get; set; } 
     public T[] Results { get; set; } 
    } 
} 
1

आप IQueryable को लपेट सकते हैं और GetEnumerator को रोक सकते हैं। इसका एक नमूना उदाहरण के लिए यहां है: http://blogs.msdn.com/b/alexj/archive/2010/03/01/tip-55-how-to-extend-an-iqueryable-by-wrapping-it.aspx। यह कुछ अलग करता है लेकिन इसे आपको विचार देना चाहिए।

इसके अलावा - कॉलर OData प्रोटोकॉल का उपयोग करके ऐसा करने के लिए URL में $ inlinecount = allpages का उपयोग कर सकता है। हालांकि मुझे यकीन नहीं है कि क्या WebAPI अभी तक इस क्वेरी विकल्प का समर्थन करता है।

+0

धन्यवाद Vitek जोड़ने के लिए एक कस्टम संदेशधारक बना रहा है - आप सही हैं कि WebAPI वर्तमान में इसका समर्थन नहीं करता है। इसके अतिरिक्त, यह RavenDB के साथ एकीकृत नहीं होगा क्योंकि यह लिंक का एक निहित हिस्सा नहीं है (जहां तक ​​मुझे पता है)। हालांकि, मैंने इसे काम करने के लिए कम प्रभावशाली तरीका निकाला है (मेरा जवाब देखें)। – Adam

+0

जिस तरह से इनलाइन गिनती आमतौर पर LINQ के साथ की जाती है वह अंतर्निहित डेटा स्टोर पर दो LINQ क्वेरी जारी करना है। वास्तविक परिणाम प्राप्त करने के लिए गिनती पाने के लिए एक और दूसरा। आप सही हैं कि वर्तमान में LINQ एक प्रश्न में ऐसा करने का समर्थन नहीं करता है। –