2012-05-22 6 views
12

Ich habe einen einfachen benutzerdefinierten Queryprovider, die einen Ausdruck nimmt, übersetzen sie in SQL und fragen eine SQL-Datenbank.Pass LINQ Ausdruck zu einem anderen Queryprovider

Ich möchte einen kleinen Cache in der Queryprovider erstellen, dass speichert Objekte häufig zugegriffen, so Abruf ohne Datenbank-Hit passieren kann.

Die Queryprovider hat die Methode

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

Der Cache als ein Feld in dieser Klasse Queryprovider sitzt und ist eine einfache generische Liste.

Wenn ich die List.AsQueryable Methode verwenden und den obigen Ausdruck in den Execute-Methode des Anbieters List.AsQueryable gebe es funktioniert nicht wie gewünscht. Es sieht so aus, als wenn ein Ausdruck kompiliert wird, wird der anfängliche QueryProvider ein integraler Bestandteil.

Ist es möglich, einen Ausdruck zu einem nachfolgenden Queryprovider und führen um den Ausdruck zu passieren, wie gewünscht?

Der Aufruf Code sieht vage wie folgt:

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 
    } 
} 

Es macht keinen Fehler zurück, anstatt sie in Schleife stecken bleibt, wo derselbe Anbieter über genannt wird und immer wieder.

Hier einige mehr Code zu zeigen, was ich zu tun habe versucht:

Collection:

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); 
    } 

} 

Abfrage:

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(); 
    } 
} 

Iterator:

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(); 
    } 
} 

Programm:

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); 
     } 

    } 
} 

Giraffe:

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

Sonderfälle z.B. SingleAndDefault usw. sind weggelassen. Der Teil, den ich arbeiten möchte, passiert in Iterator, wo er zuerst den QueryProvider der Liste ausführt, bevor er das Dictionary ausführt.

Einer der beiden Abfragbare Objekte könnten sonst eine Datenbank, oder so etwas.

+0

Konnten Sie den aufrufenden Code hinzufügen? –

+0

Telefoncode hinzugefügt – Anthony

+1

Können Sie ein Beispiel für den Linq-Ausdruck angeben, mit dem Sie den QueryProvider aufrufen? (Ich versuche, Ihren Code lokal zu rekonstruieren). Implementieren Sie auch die generische Version von Execute? 'public TResult Execute (Ausdruck System.Linq.Expressions.Expression) {...}' – CodingWithSpike

Antwort

6

Nein, eine Abfrage nicht an einen Anbieter gebunden werden. Aus diesem Grund haben Sie die IQueryable-Schnittstelle: Sie stellt sowohl den Ausdruck als auch den Provider bereit, sodass LINQ den Provider zur Ausführung des Ausdrucks aufrufen kann.

Das Problem in Ihrer Implementierung ist in der Art und Weise Query<Entity> stellt sich vor: Sie die Wurzel Ausdruck Expression.Constant(this) sind einstellen, wo this ist die Abfrage (nicht die Sammlung).

Also, wenn Sie die Abfrage mit LINQ-to-Objects ausführen, wird es GetEnumerator auf Query<>, ruft die dann rufen LINQ-to-Objects Expression, auszuführen, die eine Wurzel Ausdruck hat Expression.Constant(this) (vom Typ Query<>) und LINQ -zu-Objekte iteriert dann diese Wurzel-Expression durch GetEnumerator auf dieser Query<> Aufruf usw.

Das Problem liegt in

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

welche

grundsätzlich gleich
new Entity[0].AsQueryable().Provider.Execute(expression) 

oder

linqToObjectsProvider.Execute(expression) 

Der Anbieter von einer Abfrage zurückgegeben wird nicht mit der Quelle verbunden sind (this.cacheOne), so dass Sie nur den Ausdruck erneut ausgeführt wird, nicht Ihre Cache Abfrage über.

Was stimmt nicht mit dem Folgenden?

class Collection<Entity> 
{ 
    ... 

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

Beachten Sie, dass Concat verwendet verzögerte Auswertung, so nur, wenn Sie die Abfrage ausführen sind cacheOne und cacheTwo verkettet und dann manipuliert, um die zusätzlichen LINQ-Operatoren verwenden.

(In diesem Fall würde ich Collection<Entity> ein IQueryable with Expression equal to Expression.Constant (this.cacheOne.Concat (this.cacheTwo.Values) machen) `. Ich glaube, Sie tun können, mit allen anderen Klassen entfernt.

)

Ursprüngliche Antwort

aber ich glaube nicht, diese Art der huckepack LINQ to Objects jemals in der Lage sein zu tun, was Sie denken, es sollte.

Zumindest sollten Sie den ursprünglichen Abfrage-Anbieter behalten, so dass Sie diesen anzurufen können, wenn Sie einen Cache-Miss haben. Wenn Sie dies nicht tun und Ihren eigenen Abfrageanbieter verwenden (Sie haben den Code, den Sie für den eigentlichen Anruf verwenden, nicht angezeigt), ruft Ihr Abfrageanbieter erneut selbst auf.

So benötigen Sie einen CachingQueryProvider und ein CachingQuery zu erstellen:

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); 
    } 
} 

Auch wenn Sie ein Ergebnis gecached werden sollen, werden Sie das Ergebnis vor materialisieren müssen Sie es zwischenspeichern, Andernfalls cachen Sie die Abfrage, nicht das Ergebnis. Und das Ergebnis selbst sollte nie wieder ausgeführt werden, da es bereits die Daten sind, die Sie zurückgeben sollten.

Die Richtung ich den Kopf würde in sich wie folgt:

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; 
    } 
} 

Für den Teil markiert als Won't compile, werden Sie einige Überlegungen Tricks zu tun haben.

Und Vorsicht: String implementiert IEnumerable, also seien Sie vorsichtig nicht, um zu versuchen, einen einzelnen String Ergebniswert zu materialisieren.

+0

Danke, Ruben, das ist hilfreich, aber warum denkst du, dass LINQ-to-Objects, die mit Piggy-Backing arbeiten, niemals das tun werden, was ich mir erhofft habe? Ihr Workaround ist gut, aber ich bin neugierig, warum Sie nicht glauben, Piggy-Backing kann funktionieren. – Anthony

+0

LINQ to Objects ist zum Aufzählen von In-Memory-Sammlungen (wie Arrays) vorgesehen. Das ist das einzige, was es tun kann. Wenn Sie also LINQ to Objects eine Abfrage wie 'from entity in table wobei ... select entity' ausführen lassen, wird' table' aufgefordert, alle Elemente zurückzugeben und dann das 'where' auf das Ergebnis anzuwenden. Und 'table' verwendet dazu seinen eigenen Datenkontext (und führt bei jeder Verwendung' SELECT * FROM Table' aus). Sie müssen also die Abfrage ausführen und das Ergebnis in eine In-Memory-Struktur umwandeln und diese zwischenspeichern. Ich sehe nicht, wo L2O hier hineinpasst. – Ruben

+0

Auch 'IQueryProvider.Execute' sollte immer das * Ergebnis * der Abfrage zurückgeben. Es sollte keine Zwischenrepräsentation zurückgeben. Vielleicht ist das die Verwirrung? – Ruben

Verwandte Themen