我有以下简单的缓存类,我想用它来缓存从数据库中经常读取的项目。
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中还有另一种方法可以实现吗?
答案 0 :(得分:2)
你必须使用表达式树,而不是委托和lambdas。
将构造函数参数类型和字段类型更改为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);
添加私有帮助器方法以将键选择器表达式与相等性检查结合起来:
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
);
}
调用该方法以获得SingleOrDefault
的正确表达式:
var value = db.GetTable<TElement>().SingleOrDefault(GetConditionExpression(key));
我现在无法使用真正的数据库对其进行测试,但应该可行。如果没有,至少应该指出你正确的方向。