Ich habe einen einfachen benutzerdefinierten QueryProvider, der einen Ausdruck nimmt, ihn in SQL übersetzt und eine SQL-Datenbank abfragt.
Ich möchte einen kleinen Cache im QueryProvider erstellen, der häufig abgerufene Objekte speichert, sodass ein Abruf ohne Datenbankzugriff erfolgen kann.
Der QueryProvider hat die Methode
public object Execute(System.Linq.Expressions.Expression expression)
{
/// Erstellt eine SQL-Anweisung aus dem Ausdruck, führt sie aus und gibt übereinstimmende Objekte zurück.
}
Der Cache liegt als Feld in dieser QueryProvider-Klasse vor und ist eine einfache generische Liste.
Wenn ich die List.AsQueryable-Methode verwende und den obigen Ausdruck in die Execute-Methode des Providers von List.AsQueryable übergebe, funktioniert es nicht wie gewünscht. Es sieht so aus, als ob der ursprüngliche QueryProvider ein integraler Bestandteil wird, wenn ein Ausdruck kompiliert wird.
Ist es möglich, einen Ausdruck an einen nachfolgenden QueryProvider zu übergeben und den Ausdruck wie gewünscht auszuführen?
Der Aufrufcode sieht ungefähr wie folgt aus:
public class QueryProvider()
{
private List cache = new List();
public object Execute(System.Linq.Expressions.Expression expression)
{
/// Überprüft, ob der Ausdruck ein einzelnes oder mehrfaches Ergebnis erwartet.
bool isSingle = true;
if (isSingle)
{
var result = this.cache.AsQueryable().Provider.Execute(expression);
if (result != null)
return result;
}
/// Cache fehlgeschlagen, Datenbankabfrage
var qt = new QueryTranslator();
string sql = qt.Translate(expression);
/// .... Datenbankabfrage
}
}
Es wird kein Fehler zurückgegeben, stattdessen bleibt es in einer Schleife stecken, in der dieser Provider immer wieder aufgerufen wird.
Hier ist etwas weiterer Code, der zeigt, was ich versuche zu tun:
Sammlung:
class Collection
{
internal List cacheOne { get; private set; }
internal Dictionary cacheTwo { get; private set; }
internal Collection()
{
this.cacheOne = new List();
this.cacheTwo = new Dictionary();
}
public IQueryable Query()
{
return new Query(this.cacheOne, this.cacheTwo);
}
}
Abfrage:
class Query : IQueryable
{
internal Query(List cacheOne, Dictionary cacheTwo)
{
this.Provider = new QueryProvider(cacheOne, cacheTwo);
this.Expression = Expression.Constant(this);
}
internal Query(IQueryProvider provider, Expression expression)
{
this.Provider = provider;
if (expression != null)
this.Expression = expression;
}
public IEnumerator GetEnumerator()
{
return this.Provider.Execute>(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 : IQueryProvider
{
private List cacheOne;
private Dictionary cacheTwo;
internal QueryProvider(List cacheOne, Dictionary cacheTwo)
{
this.cacheOne = cacheOne;
this.cacheTwo = cacheTwo;
}
public IQueryable CreateQuery(System.Linq.Expressions.Expression expression)
{
return new Query(this, expression);
}
public IQueryable CreateQuery(System.Linq.Expressions.Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute(System.Linq.Expressions.Expression expression)
{
return (TResult)this.Execute(expression);
}
public object Execute(System.Linq.Expressions.Expression expression)
{
Iterator iterator = new Iterator(expression, cacheOne, cacheTwo);
return (iterator as IEnumerable).GetEnumerator();
}
}
Iterator:
class Iterator : IEnumerable
{
private Expression expression;
private List cacheOne;
private Dictionary cacheTwo;
internal Iterator(Expression expression, List cacheOne, Dictionary cacheTwo)
{
this.expression = expression;
this.cacheOne = cacheOne;
this.cacheTwo = cacheTwo;
}
public IEnumerator GetEnumerator()
{
foreach (var result in (IEnumerable)this.cacheOne.AsQueryable().Provider.Execute(expression))
{
yield return result;
}
foreach (var more in (IEnumerable)this.cacheTwo.Values.AsQueryable().Provider.Execute(expression))
{
yield return more;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
Programm:
class Program
{
static void Main(string[] args)
{
/// Erstelle Sammlung + Caches
var collection = new Collection();
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(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);
/// Iteriere durch Giraffen, die vor einem bestimmten Datum geboren wurden
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; }
}
Spezialfälle wie SingleAndDefault usw. werden ausgelassen. Der Teil, der funktionieren soll, liegt im Iterator, wo zunächst der QueryProvider der Liste und dann der des Dictionaries ausgeführt wird.
Eines der beiden Queryable-Objekte könnte eine Datenbank oder etwas anderes sein.