c# - 自动编译 Linq 查询

标签 c# asp.net-mvc linq linq-to-sql iqueryable

我们发现 compiling our Linq queries比他们每次都必须编译要快得多,所以我们想开始使用编译查询。问题在于它使代码更难阅读,因为查询的实际语法在其他文件中已关闭,远离使用它的地方。

我想到可以编写一种方法(或扩展方法),使用反射来确定传入的查询并自动缓存编译版本以供将来使用。

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

Cached()必须反射(reflect)传入的查询对象并确定选择的表和查询的参数类型。显然,反射有点慢,所以为缓存对象使用名称可能会更好(但您仍然必须在第一次编译查询时使用反射)。

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

有没有人有这方面的经验,或者知道这是否可能?

更新:对于那些还没有看到它的人,您可以使用以下代码将 LINQ 查询编译为 SQL:

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

我想做的是缓存这些 Func<>第一次自动编译查询后我可以调用的对象。

最佳答案

您不能在匿名 lambda 表达式上调用扩展方法,因此您需要使用 Cache 类。为了正确缓存查询,您还需要将任何参数(包括您的 DataContext)“提升”到 lambda 表达式的参数中。这会导致非常冗长的用法,例如:

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

为了清理它,如果我们使它成为非静态的,我们可以在每个上下文的基础上实例化一个 QueryCache:

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

然后我们可以编写一个缓存方法,使我们能够编写以下内容:

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

您查询中的任何参数也需要被取消:

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

这是我模拟的 QueryCache 实现:

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

这可以扩展以支持更多参数。最重要的是,通过将参数值传递给 Cache 方法本身,您可以隐式输入 lambda 表达式。

编辑:请注意,您不能将新的运算符应用于已编译的查询。具体来说,您不能这样做:

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

因此,如果您计划对查询进行分页,则需要在编译操作中进行,而不是稍后进行。这不仅对于避免异常是必要的,而且为了与 Skip/Take 的整个要点保持一致(以避免从数据库返回所有行)。这种模式可行:

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

另一种分页方法是返回一个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);
}

此模式的用法如下:

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

关于c# - 自动编译 Linq 查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1223771/

相关文章:

c# - 带有两个 where 子句的 Linq 语句

c# - 抛出异常的最佳方式

c# - 为什么文件不能通过 tcp 在 C# 中的客户端和 C 中的服务器之间正确传输(保存?)?

c# - 你可以传递没有类型参数的泛型委托(delegate)吗?

linq - 如何在 LINQ 中正确使用 GroupBy?

c# - 如何对列表进行排序然后对该列表的子集进行排序

c# - SignInManager.PasswordSignInAsync 上的“没有为此 DbContext 配置数据库提供程序”

c# - 等待n秒,然后下一行代码不卡住表格

html - asp mvc 默认生成的应用程序删除空格

.net - 如何将包含日期的字符串转换为不同的格式?