为IQueryable动态生成的查找键

时间:2014-02-08 00:29:40

标签: c# sql linq linq-to-sql

我有以下简单的缓存类,我想用它来缓存从数据库中经常读取的项目。

public class Cache<TKey, TElement>
    where TElement : class
{
    private Dictionary<TKey, TElement> cache = new Dictionary<TKey, TElement>();
    private Func<TElement, TKey> dbKey;

    public Cache(Func<TElement, TKey> dbKey)
    {
        this.dbKey = dbKey;
    }

    public TElement Get(TKey key, DataContext db)
    {
        if (cache.ContainsKey(key)) return cache[key];

        // This line throws exception.  See below
        var value = db.GetTable<TElement>().SingleOrDefault(x => dbKey(x).Equals(key));

        if (value != null) cache[key] = value;
        return value;
    }
}

用法如下:

var db = new DataContext();
var userCache = new Cache<string, User>(u => u.Username);
userCache.Get("dave", db);

但是,指示的行抛出NotSupportedException并显示消息“Method'System.Object DynamicInvoke(System.Object [])'没有支持的SQL转换。”我理解为什么会抛出异常,但我不确定如何解决它。

缓存旨在在许多不同的db表之前运行,其中不同的列用于查找键。我的想法是传入一个lambda来指定查找列,正如您在代码中看到的那样。这适用于IEnumerable,但不适用于IQueryable。

在LINQ to SQL中还有另一种方法可以实现吗?

1 个答案:

答案 0 :(得分:2)

你必须使用表达式树,而不是委托和lambdas。

  1. 将构造函数参数类型和字段类型更改为Expression<Func<TElement, TKey>>

    private Expression<Func<TElement, TKey>> dbKey;
    
    public Cache(Expression<Func<TElement, TKey>> dbKey)
    {
        this.dbKey = dbKey;
    }
    

    感谢编译器在编译时从lambda表达式自动生成表达式树,你仍然可以像以前一样调用它:

    var userCache = new Cache<string, User>(u => u.Username);
    
  2. 添加私有帮助器方法以将键选择器表达式与相等性检查结合起来:

    private Expression<Func<TElement, bool>> GetConditionExpression(TKey key)
    {
        var param = Expression.Parameter(typeof(TElement));
    
        return
            Expression.Lambda<Func<TElement, bool>>(
                Expression.Equal(
                    Expression.Invoke(
                        dbKey,
                        param
                    ),
                    Expression.Constant(key)
                ),
            param
            );
    }
    
  3. 调用该方法以获得SingleOrDefault的正确表达式:

    var value = db.GetTable<TElement>().SingleOrDefault(GetConditionExpression(key));
    
  4. 我现在无法使用真正的数据库对其进行测试,但应该可行。如果没有,至少应该指出你正确的方向