2012-05-22 6 views
12

पर LINQ अभिव्यक्ति पास करें मेरे पास एक साधारण कस्टम क्वेरीप्रोवाइडर है जो अभिव्यक्ति लेता है, इसे SQL में अनुवाद करता है और एक SQL डेटाबेस से पूछताछ करता है।अन्य क्वेरीरीप्रोडर

मैं क्वेरीरीप्रोवाइडर में एक छोटा कैश बनाना चाहता हूं जो आमतौर पर एक्सेस की गई वस्तुओं को संग्रहीत करता है ताकि डाटाबेस हिट के बिना पुनर्प्राप्ति हो सके।

QueryProvider विधि

public object Execute(System.Linq.Expressions.Expression expression) 
{ 
    /// Builds an SQL statement from the expression, 
    /// executes it and returns matching objects 
} 

कैश इस QueryProvider कक्षा में एक क्षेत्र के रूप में बैठता है और एक सरल सामान्य सूची है।

यदि मैं List.AsQueryable विधि का उपयोग करता हूं और उपरोक्त अभिव्यक्ति को सूची में पास करता हूं। ऑक्साइबल के प्रदाता की निष्पादन विधि यह वांछित के रूप में काम नहीं करती है। ऐसा लगता है कि एक अभिव्यक्ति संकलित हो जाती है जब प्रारंभिक क्वेरीप्रोवाइडर एक अभिन्न हिस्सा बन जाता है।

क्या बाद में क्वेरीप्रोवाइडर को अभिव्यक्ति पारित करना संभव है और वांछित अभिव्यक्ति को निष्पादित करना संभव है? इस प्रकार

बुला कोड अस्पष्ट लग रहा है:

public class QueryProvider<Entity>() 
{ 
    private List<TEntity> cache = new List<Entity>(); 

    public object Execute(System.Linq.Expressions.Expression expression) 
    { 
     /// check whether expression expects single or multiple result 
     bool isSingle = true; 

     if (isSingle) 
     { 
      var result = this.cache.AsQueryable<Entity>().Provider.Execute(expression); 
      if (result != null) 
       return result; 
     } 

     /// cache failed, hit database 
     var qt = new QueryTranslator(); 
     string sql = qt.Translate(expression); 
     /// .... hit database 
    } 
} 

यह एक त्रुटि वापस नहीं करता है, बजाय इसे पाश जहां यह एक ही प्रदाता बार बार कहा जाता है में फंस जाता है।

यहां कुछ और कोड मैं क्या करने की कोशिश कर रहा हूँ दिखा रहा है:

संग्रह:

class Collection<Entity> 
{ 

    internal List<Entity> cacheOne { get; private set; } 
    internal Dictionary<Guid, Entity> cacheTwo { get; private set; } 

    internal Collection() 
    { 
     this.cacheOne = new List<Entity>(); 
     this.cacheTwo = new Dictionary<Guid, Entity>(); 
    } 

    public IQueryable<Entity> Query() 
    { 
     return new Query<Entity>(this.cacheOne, this.cacheTwo); 
    } 

} 

क्वेरी:

class Query<Entity> : IQueryable<Entity> 
{ 
    internal Query(List<Entity> cacheOne, Dictionary<Guid, Entity> cacheTwo) 
    { 
     this.Provider = new QueryProvider<Entity>(cacheOne, cacheTwo); 
     this.Expression = Expression.Constant(this); 
    } 

    internal Query(IQueryProvider provider, Expression expression) 
    { 
     this.Provider = provider; 
     if (expression != null) 
      this.Expression = expression; 
    } 

    public IEnumerator<Entity> GetEnumerator() 
    { 
     return this.Provider.Execute<IEnumerator<Entity>>(this.Expression); 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 

    public Type ElementType 
    { 
     get { return typeof(Entity); } 
    } 

    public System.Linq.Expressions.Expression Expression { get; private set; } 

    public IQueryProvider Provider { get; private set; } 
} 

QueryProvider:

class QueryProvider<Entity> : IQueryProvider 
{ 

    private List<Entity> cacheOne; 
    private Dictionary<Guid, Entity> cacheTwo; 

    internal QueryProvider(List<Entity> cacheOne, Dictionary<Guid, Entity> cacheTwo) 
    { 
     this.cacheOne = cacheOne; 
     this.cacheTwo = cacheTwo; 
    } 

    public IQueryable<TElement> CreateQuery<TElement>(System.Linq.Expressions.Expression expression) 
    { 
     return new Query<TElement>(this, expression); 
    } 

    public IQueryable CreateQuery(System.Linq.Expressions.Expression expression) 
    { 
     throw new NotImplementedException(); 
    } 

    public TResult Execute<TResult>(System.Linq.Expressions.Expression expression) 
    { 
     return (TResult)this.Execute(expression); 
    } 

    public object Execute(System.Linq.Expressions.Expression expression) 
    { 
     Iterator<Entity> iterator = new Iterator<Entity>(expression, cacheOne, cacheTwo); 
     return (iterator as IEnumerable<Entity>).GetEnumerator(); 
    } 
} 

इटरेटर:

class Iterator<Entity> : IEnumerable<Entity> 
{ 
    private Expression expression; 
    private List<Entity> cacheOne; 
    private Dictionary<Guid, Entity> cacheTwo; 

    internal Iterator(Expression expression, List<Entity> cacheOne, Dictionary<Guid, Entity> cacheTwo) 
    { 
     this.expression = expression; 
     this.cacheOne = cacheOne; 
     this.cacheTwo = cacheTwo; 
    } 

    public IEnumerator<Entity> GetEnumerator() 
    { 
     foreach (var result in (IEnumerable<Entity>)this.cacheOne.AsQueryable<Entity>().Provider.Execute(expression)) 
     { 
      yield return result; 
     } 

     foreach (var more in (IEnumerable<Entity>)this.cacheTwo.Values.AsQueryable<Entity>().Provider.Execute(expression)) 
     { 
      yield return more; 
     } 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 
} 

कार्यक्रम:

class Program 
{ 
    static void Main(string[] args) 
    { 
     /// Create collection + caches 
     var collection = new Collection<Giraffe>(); 
     collection.cacheOne.AddRange(new Giraffe[] { 
      new Giraffe() { Id = Guid.NewGuid(), DateOfBirth = new DateTime(2011, 03, 21), Height = 192, Name = "Percy" }, 
      new Giraffe() { Id = Guid.NewGuid(), DateOfBirth = new DateTime(2005, 12, 25), Height = 188, Name = "Santa" }, 
      new Giraffe() { Id = Guid.NewGuid(), DateOfBirth = new DateTime(1999, 04, 01), Height=144, Name="Clown" } 
     }); 
     var cachetwo = new List<Giraffe>(new Giraffe[] { 
      new Giraffe() { Id = Guid.NewGuid(), DateOfBirth = new DateTime(1980, 03,03), Height = 599, Name="Big Ears" }, 
      new Giraffe() { Id = Guid.NewGuid(), DateOfBirth = new DateTime(1985, 04, 02), Height= 209, Name="Pug" } 
     }); 
     foreach (var giraffe in cachetwo) 
      collection.cacheTwo.Add(giraffe.Id, giraffe); 

     /// Iterate through giraffes born before a certain date 
     foreach (var result in collection.Query().Where(T => T.DateOfBirth < new DateTime(2006, 01, 01))) 
     { 
      Console.WriteLine(result.Name); 
     } 

    } 
} 

जिराफ:

class Giraffe 
{ 
    public Guid Id { get; set; } 
    public string Name { get; set; } 
    public long Height { get; set; } 
    public DateTime DateOfBirth { get; set; } 
} 

विशेष मामलों उदा सिंगल एंड डिफॉल्ट, इत्यादि छोड़ दिए गए हैं। जिस हिस्से में मैं काम करना चाहता हूं वह इटरेटर में होता है, जहां यह सबसे पहले शब्दकोश के निष्पादन से पहले सूची के क्वेरीप्रोवाइडर को निष्पादित करता है।

दो क्वेरी योग्य वस्तुओं में से एक डेटाबेस हो सकता है, या कुछ और।

+0

क्या आप कॉलिंग कोड जोड़ सकते हैं? –

+0

कॉलिंग कोड जोड़ा गया – Anthony

+1

क्या आप लिंक अभिव्यक्ति का उदाहरण दे सकते हैं जिसका उपयोग आप अपने QueryProvider को कॉल करने के लिए कर रहे हैं? (मैं स्थानीय रूप से आपके कोड को पुनर्निर्माण करने की कोशिश कर रहा हूं)। साथ ही, क्या आप निष्पादन के सामान्य संस्करण को भी लागू करते हैं? 'सार्वजनिक ट्रेशल्ट निष्पादन (System.Linq.Expressions.Expression अभिव्यक्ति) {...}' – CodingWithSpike

उत्तर

6

नहीं, कोई प्रश्न प्रदाता के लिए बाध्य नहीं होता है। यही कारण है कि आपके पास IQueryable इंटरफ़ेस है: यह अभिव्यक्ति और प्रदाता दोनों प्रदान करता है, इसलिए LINQ अभिव्यक्ति को निष्पादित करने के लिए प्रदाता को कॉल कर सकता है।

अपने कार्यान्वयन में समस्या रास्ता Query<Entity> में है ही प्रतिनिधित्व करता है: आप Expression.Constant(this), जहां thisक्वेरी (नहीं संग्रह) है के लिए रूट अभिव्यक्ति स्थापित कर रहे हैं।

तो जब आप के साथ क्वेरी को निष्पादित LINQ करने वाली वस्तुओं, यह GetEnumeratorQuery<> है, जो तब कॉल पर कॉल करेंगे LINQ करने वाली वस्तुओं Expression, जो Expression.Constant(this) (प्रकार Query<> की) एक रूट अभिव्यक्ति है, और LINQ निष्पादित करने के लिए करने वाली वस्तुओं तो यह Query<> पर GetEnumerator कॉल करना, आदि द्वारा इस रूट अभिव्यक्ति iterates

समस्या

(IEnumerable<Entity>)this.cacheOne.AsQueryable<Entity>().Provider.Execute(expression) 

जो मूल रूप से

के बराबर है में निहित है तो तुम सिर्फ अभिव्यक्ति फिर से क्रियान्वित कर रहे हैं, न अपने कैश से अधिक की क्वेरी
new Entity[0].AsQueryable().Provider.Execute(expression) 

या

linqToObjectsProvider.Execute(expression) 

प्रदाता एक क्वेरी द्वारा रिटर्न, नहीं स्रोत (this.cacheOne) से जुड़ा हुआ है।

निम्नलिखित में क्या गलत है?

class Collection<Entity> 
{ 
    ... 

    public IQueryable<Entity> Query() 
    { 
     return this.cacheOne.Concat(this.cacheTwo.Values).AsQueryable(); 
    } 
} 

नोट Concat देरी मूल्यांकन, इसलिए केवल जब आप क्वेरी को निष्पादित cacheOne कर रहे हैं और cacheTwo concatenated और फिर अतिरिक्त LINQ ऑपरेटर्स का उपयोग चालाकी का उपयोग करता है।

(जो मामले में, मुझे Collection<Entity> बनाने चाहते हैं एक IQueryable with अभिव्यक्ति equal to Expression.Constant (this.cacheOne.Concat (this.cacheTwo.Values)) `। मुझे लगता है कि आप दूर अन्य सभी वर्गों के साथ कर सकते हैं।)


मूल जवाब

हालांकि, मुझे नहीं लगता कि वस्तुओं के लिए सूअर का बच्चा समर्थन LINQ का इस तरह से कभी आप क्या सोचते हैं यह होना चाहिए ऐसा करने में सक्षम हो जाएगा।

कम से कम, आपको मूल क्वेरी प्रदाता रखना चाहिए ताकि आप कैश मिस होने पर उसे कॉल कर सकें। यदि आप नहीं करते हैं, और अपने स्वयं के क्वेरी प्रदाता का उपयोग करते हैं (आपने उस कोड को नहीं दिखाया जिसका उपयोग आप वास्तविक कॉल करने के लिए कर रहे हैं), तो आपका क्वेरी प्रदाता एक बार फिर से कॉल करेगा।

तो तुम एक CachingQueryProvider बनाने की आवश्यकता होगी और एक CachingQuery:

class CachingQuery<T> : IQueryable<T> 
{ 
    private readonly CachingQueryProvider _provider; 
    private readonly Expression _expression; 

    public CachingQuery(CachingQueryProvider provider, Expression expression) 
    { 
     _provider = provider; 
     _expression = expression; 
    } 

    // etc. 
} 

class CachingQueryProvider : IQueryProvider 
{ 
    private readonly IQueryProvider _original; 

    public CachingQueryProvider(IQueryProvider original) 
    { 
     _original = original; 
    } 

    // etc. 
} 

public static class CachedQueryable 
{ 
    public static IQuerable<T> AsCached(this IQueryable<T> source) 
    { 
     return new CachingQuery<T>(
      new CachingQueryProvider(source.Provider), 
      source.Expression); 
    } 
} 

इसके अलावा, अगर आप एक परिणाम कैश करने के लिए चाहते हैं, आप परिणाम अमल में लाना करने के लिए इससे पहले कि आप कैश की आवश्यकता होगी, अन्यथा आप क्वेरी को कैश करते हैं, परिणाम नहीं। और नतीजा खुद को कभी भी निष्पादित नहीं किया जाना चाहिए, क्योंकि यह पहले से ही डेटा है जिसे आपको वापस करना चाहिए।

दिशा मैं में सिर होगा इस प्रकार है:

class CachingQueryProvider : IQueryProvider 
{ 
    public object Execute(Expression expression) 
    { 
     var key = TranslateExpressionToCacheKey(expression); 

     object cachedValue; 
     if (_cache.TryGetValue(key, out cachedValue)) 
      return cachedValue; 

     object result = _originalProvider.Execute(expression); 

     // Won't compile because we don't know T at compile time 
     IEnumerable<T> sequence = result as IEnumerable<T>; 
     if (sequence != null && !(sequence is ICollection<T>)) 
     { 
      result = sequence.ToList<T>(); 
     } 

     _cache[key] = result; 

     return result; 
    } 
} 

भाग के लिए चिह्नित Won't compile के रूप में, आप कुछ प्रतिबिंब प्रवंचना करना होगा।

और सावधानी: स्ट्रिंग IENumerable लागू करता है, इसलिए एक स्ट्रिंग परिणाम मान को पूरा करने के लिए पर सावधान रहें।

+0

धन्यवाद रूबेन, यह सहायक है लेकिन आपको क्यों लगता है कि पिग्गी-बैकिंग LINQ-to-Objects कभी भी ऐसा नहीं करेगा जो मैं उम्मीद कर रहा हूं? आपका कामकाज अच्छा है लेकिन मुझे उत्सुकता है कि आपको ऐसा क्यों लगता है कि पिग्गी-बैकिंग काम नहीं कर सकती है। – Anthony

+0

LINQ से ऑब्जेक्ट्स इन-मेमोरी संग्रह (जैसे सरणी) की गणना के लिए है। यही एकमात्र चीज है जो यह कर सकती है। तो यदि आप LINQ को ऑब्जेक्ट्स को 'तालिका में इकाई से ...' इकाई का चयन करें 'जैसे क्वेरी निष्पादित करते हैं, तो यह सभी तत्वों को वापस करने के लिए' तालिका 'से पूछेगा, और फिर परिणाम के लिए' कहां 'लागू करेगा। और 'टेबल' ऐसा करने के लिए अपने स्वयं के डेटा संदर्भ का उपयोग करेगा (और इस प्रकार हर बार जब आप इसे इस्तेमाल करते हैं' तालिका से चुनें * निष्पादित करें)। इसलिए आपको क्वेरी निष्पादित करनी होगी और परिणाम को मेमोरी स्ट्रक्चर में बदलना होगा और कैश करना होगा। मैं नहीं देखता कि एल 2 ओ यहां फिट बैठता है। – Ruben

+0

भी 'IQueryProvider.Execute' हमेशा क्वेरी के * परिणाम * को वापस करना चाहिए। यह एक मध्यवर्ती प्रतिनिधित्व वापस नहीं करना चाहिए। शायद यह भ्रम है? – Ruben