36 votes

Compiler automatiquement les requêtes Linq

Nous avons constaté que la compilation de nos requêtes Linq est beaucoup, beaucoup plus rapide que d'avoir à compiler à chaque fois, donc nous aimerions commencer à utiliser les requêtes compilées. Le problème est que cela rend le code plus difficile à lire, car la syntaxe de la requête est éteint dans un autre fichier, de là où il est utilisé.

Il m'est apparu qu'il pourrait être possible d'écrire une méthode (ou une extension de la méthode) qui utilise la réflexion pour déterminer quelles requêtes sont passés dans et cache les versions compilées automatiquement pour une utilisation dans l'avenir.

var foo = (from f in db.Foo where f.ix == bar select f).Cached();

Cached() aurait pour refléter la requête de l'objet passé en et de déterminer la / les table(s) sélectionné et les types de paramètres de la requête. De toute évidence, la réflexion est un peu lent, de sorte qu'il pourrait être préférable d'utiliser des noms de l'objet dans le cache (mais que vous souhaitez toujours avoir à utiliser la réflexion la première fois à la compilation de la requête).

var foo = (from f in db.Foo where f.ix == bar select f).Cached("Foo.ix");

Quelqu'un a une expérience avec cette, ou de savoir si c'est encore possible?

Mise à JOUR: Pour ceux qui ne l'ont pas vu, vous pouvez compiler des requêtes LINQ to SQL avec le code suivant:

public static class MyCompiledQueries
{
    public static Func<DataContext, int, IQueryable<Foo>> getFoo =
        CompiledQuery.Compile(
            (DataContext db, int ixFoo) => (from f in db.Foo
                                            where f.ix == ixFoo
                                            select f)
        );
}

Ce que j'essaie de faire est d'avoir un cache de ces Func<> des objets que je peux appeler automatiquement après la compilation de la requête la première fois autour.

18voto

Jason Points 11332

Vous ne pouvez pas avoir des méthodes d'extension appelée sur anonyme lambda expressions, de sorte que vous aurez envie d'utiliser une classe de Cache. Afin de bien mettre en cache une requête, vous aurez également besoin d "ascenseur' tous les paramètres (y compris votre DataContext) dans les paramètres de votre expression lambda. Il en résulte très détaillé de l'utilisation, comme:

var results = QueryCache.Cache((MyModelDataContext db) => 
    from x in db.Foo where !x.IsDisabled select x);

Afin de nettoyer tout ça, on peut instancier un QueryCache par le contexte, si nous le rendre non-statique:

public class FooRepository
{
    readonly QueryCache<MyModelDataContext> q = 
        new QueryCache<MyModelDataContext>(new MyModelDataContext());
}

Ensuite, nous pouvons écrire un Cache méthode qui va nous permettre d'écrire ce qui suit:

var results = q.Cache(db => from x in db.Foo where !x.IsDisabled select x);

Tous les arguments dans votre requête devra également être levé:

var results = q.Cache((db, bar) => 
    from x in db.Foo where x.id != bar select x, localBarValue);

Voici la QueryCache mise en œuvre j'ai fait la maquette:

public class QueryCache<TContext> where TContext : DataContext
{
    private readonly TContext db;
    public QueryCache(TContext db)
    {
        this.db = db;
    }

    private static readonly Dictionary<string, Delegate> cache = new Dictionary<string, Delegate>();

    public IQueryable<T> Cache<T>(Expression<Func<TContext, IQueryable<T>>> q)
    {
        string key = q.ToString();
        Delegate result;
        lock (cache) if (!cache.TryGetValue(key, out result))
        {
            result = cache[key] = CompiledQuery.Compile(q);
        }
        return ((Func<TContext, IQueryable<T>>)result)(db);
    }

    public IQueryable<T> Cache<T, TArg1>(Expression<Func<TContext, TArg1, IQueryable<T>>> q, TArg1 param1)
    {
        string key = q.ToString();
        Delegate result;
        lock (cache) if (!cache.TryGetValue(key, out result))
        {
            result = cache[key] = CompiledQuery.Compile(q);
        }
        return ((Func<TContext, TArg1, IQueryable<T>>)result)(db, param1);
    }

    public IQueryable<T> Cache<T, TArg1, TArg2>(Expression<Func<TContext, TArg1, TArg2, IQueryable<T>>> q, TArg1 param1, TArg2 param2)
    {
        string key = q.ToString();
        Delegate result;
        lock (cache) if (!cache.TryGetValue(key, out result))
        {
            result = cache[key] = CompiledQuery.Compile(q);
        }
        return ((Func<TContext, TArg1, TArg2, IQueryable<T>>)result)(db, param1, param2);
    }
}

Cela peut être étendu pour prendre en charge plus d'arguments. Le grand bit est qu'en transmettant les valeurs de paramètres dans la mémoire Cache de la méthode elle-même, vous obtenez de typage implicite de l'expression lambda.

EDIT: Notez que vous ne pouvez pas appliquer de nouveaux opérateurs pour les requêtes compilées.. Spécifiquement vous ne pouvez pas faire quelque chose comme ceci:

var allresults = q.Cache(db => from f in db.Foo select f);
var page = allresults.Skip(currentPage * pageSize).Take(pageSize);

Donc, si vous envisagez sur la pagination d'une requête, vous devez le faire dans l'opération de la compilation au lieu de le faire plus tard. Ceci est nécessaire non seulement pour éviter une exception, mais également en conformité avec le point de l'ensemble de Skip/Prendre (pour éviter de se retrouver toutes les lignes de la base de données). Ce modèle devrait fonctionner:

public IQueryable<Foo> GetFooPaged(int currentPage, int pageSize)
{
    return q.Cache((db, cur, size) => (from f in db.Foo select f)
        .Skip(cur*size).Take(size), currentPage, pageSize);
}

Une autre approche de la pagination serait de revenir à un Func:

public Func<int, int, IQueryable<Foo>> GetPageableFoo()
{
    return (cur, size) => q.Cache((db, c, s) => (from f in db.foo select f)
        .Skip(c*s).Take(s), c, s);
}

Ce modèle est utilisé comme:

var results = GetPageableFoo()(currentPage, pageSize);

2voto

Stan R. Points 8967

Puisque personne ne tente, je vais tenter le coup. Peut-être pouvons-nous résoudre ce problème d'une manière ou d'une autre. Voici ma tentative à ceci.

J'ai mis cela en place à l'aide d'un dictionnaire, je n'utilise pas non plus DataContext bien que ce soit trivial, je crois.

 public static class CompiledExtensions
    {
        private static Dictionary<string, object> _dictionary = new Dictionary<string, object>();

        public static IEnumerable<TResult> Cache<TArg, TResult>(this IEnumerable<TArg> list, string name, Expression<Func<IEnumerable<TArg>, IEnumerable<TResult>>> expression)
        {
            Func<IEnumerable<TArg>,IEnumerable<TResult>> _pointer;

            if (_dictionary.ContainsKey(name))
            {
                _pointer = _dictionary[name] as Func<IEnumerable<TArg>, IEnumerable<TResult>>;
            }
            else
            {
                _pointer = expression.Compile();
                _dictionary.Add(name, _pointer as object);
            }

            IEnumerable<TResult> result;
            result = _pointer(list);

            return result;
        }
    }
 

maintenant cela me permet de le faire

   List<string> list = typeof(string).GetMethods().Select(x => x.Name).ToList();

  IEnumerable<string> results = list.Cache("To",x => x.Where( y => y.Contains("To")));
  IEnumerable<string> cachedResult = list.Cache("To", x => x.Where(y => y.Contains("To")));
  IEnumerable<string> anotherCachedResult = list.Cache("To", x => from item in x where item.Contains("To") select item);
 

dans l'attente d'une discussion à ce sujet, afin de développer cette idée.

1voto

Simon_Weaver Points 31141

Pour la postérité future: .NET Framework 4.5 le fera par défaut (selon une diapositive d'une présentation que je viens de regarder).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X