Automatycznie Kompiluje Zapytania Linq

Odkryliśmy, że kompilowanie zapytań Linq jest dużo, dużo szybsze niż kompilowanie za każdym razem, więc chcielibyśmy zacząć używać zapytań skompilowanych. Problem polega na tym, że utrudnia to odczyt kodu, ponieważ rzeczywista składnia zapytania jest wyłączona w innym pliku, z dala od miejsca, w którym jest używana.

Przyszło mi do głowy, że możliwe jest napisanie metody (lub metody rozszerzenia), która używa refleksji, aby określić, jakie zapytania są przekazywane i buforować skompilowane wersje automatycznie do wykorzystania w przyszłości.

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

Cached() musiałby odzwierciedlać przekazywany obiekt zapytania i określać tabele wybrane NA oraz typy parametrów dla zapytania. Oczywiście, reflection jest trochę powolne, więc może być lepiej używać nazw dla obiektu cache (ale nadal będziesz musiał użyć reflection za pierwszym razem, aby skompilować zapytanie).

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

Czy ktoś ma jakieś doświadczenie z robieniem tego, lub wie, czy to nawet możliwe?

UPDATE: dla tych, którzy go nie widzieli, możesz skompilować zapytania LINQ do SQL z następującym kodem:

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

To, co próbuję zrobić, to mieć pamięć podręczną tych Func<> obiektów, które mogę wywołać po automatycznym skompilowaniu zapytania za pierwszym razem.

Author: Jason, 2009-08-03

3 answers

Nie możesz wywoływać metod rozszerzeń na anonimowych wyrażeniach lambda, więc będziesz chciał użyć klasy Cache. Aby poprawnie buforować zapytanie, musisz również "podnieść" wszelkie parametry (w tym DataContext) do parametrów dla wyrażenia lambda. Powoduje to bardzo wyraziste użycie, takie jak:

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

Aby to wyczyścić, możemy utworzyć instancję QueryCache na podstawie kontekstu, jeśli uczynimy go niestatycznym:

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

Wtedy możemy napisać metodę Cache, która pozwoli nam napisać:

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

Wszelkie argumenty w zapytaniu również będą musiały zostać usunięte:

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

Oto implementacja QueryCache, którą wyśmiałem:

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

Można to rozszerzyć, aby wspierać więcej argumentów. Wielkim bitem jest to, że przekazując wartości parametrów do samej metody Cache, otrzymujesz niejawne wpisywanie wyrażenia lambda.

EDIT: zauważ, że nie możesz zastosować nowych operatorów do skompilowanych zapytań.. W szczególności nie możesz zrób coś takiego:

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

Więc jeśli planujesz stronicowanie zapytania, musisz to zrobić w operacji kompilacji zamiast robić to później. Jest to konieczne nie tylko w celu uniknięcia wyjątku, ale również zgodnie z całym punktem Skip/Take (aby uniknąć zwracania wszystkich wierszy z bazy danych). Ten wzór zadziała:

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

Innym podejściem do przywoływania byłoby zwrócenie 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);
}

Ten wzór jest używany w następujący sposób:

var results = GetPageableFoo()(currentPage, pageSize);
 18
Author: Jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-04 15:46:10

Skoro nikt nie próbuje, spróbuję. Może oboje jakoś to rozwiążemy. Oto moja próba.

Skonfigurowałem to za pomocą słownika, nie używam również DataContext, chociaż uważam, że jest to banalne.

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

Teraz to pozwala mi to zrobić

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

Czekam na jakąś dyskusję na ten temat, aby dalej rozwijać tę ideę.

 2
Author: Stan R.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-04 14:38:42

Dla przyszłych potomności:. NET Framework 4.5 zrobi to domyślnie (zgodnie z slajdem w prezentacji, którą właśnie oglądałem).

 1
Author: Simon_Weaver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-16 18:24:32