2012-03-17 24 views
10

我已經看過與此類似的其他問題,但找不到任何可行的答案。將lambda表達式轉換爲緩存的唯一鍵

我一直在使用下面的代碼來生成唯一鍵,用於將我的linq查詢的結果存儲到緩存中。

string key = ((LambdaExpression)expression).Body.ToString(); 

    foreach (ParameterExpression param in expression.Parameters) 
    { 
     string name = param.Name; 
     string typeName = param.Type.Name; 

     key = key.Replace(name + ".", typeName + "."); 
    } 

    return key; 

它似乎適用於包含整數或布爾值的簡單查詢,但當我的查詢包含嵌套的常量表達式例如

// Get all the crops on a farm where the slug matches the given slug. 
(x => x.Crops.Any(y => slug == y.Slug) && x.Deleted == false) 

返回的關鍵是這樣的:

(真AndAlso(Farm.Crops.Any(Y => (值(OzFarmGuide.Controllers.FarmController + <> c__DisplayClassd).slug == ySlug))AndAlso(Farm.Deleted == False)))

正如您所看到的,我通過的任何農作物名稱都會給出相同的關鍵結果。有沒有一種方法可以提取給定參數的值,以便我可以區分我的查詢?

而且轉換y說正確的類型名稱將是很好.....

+0

什麼是錯用'GetHashCode()'方法和'HashSet '?它不是唯一的,但大多數情況下'HashSet'能夠接收和添加O(1)中的項目。 – 2012-03-17 03:16:48

+0

@CommuSoft,這是行不通的,因爲即使看起來完全相同的兩個表達式也不會被認爲是相等的(除非你提供了你自己的相等比較器)。 – svick 2012-03-17 04:08:00

+0

@CommuSoft - 此外,哈希碼不保證是唯一的,因此您的代碼中的潛在錯誤 – Polity 2012-03-17 05:20:08

回答

5

由於政體和Marc在他們的意見,你需要的是LINQ表達的部分評價說。你可以閱讀Matt Warren's LINQ: Building an IQueryable Provider - Part III中的ExpressionVisitor。文章Caching the results of LINQ queries by Pete Montgomery(由Polity鏈接)描述了關於這種緩存的一些更具體的內容,例如,如何在查詢中表示集合。

此外,我不確定我會依靠ToString()這樣。我認爲這主要是爲了調試目的,它可能會在未來發生變化。另一種方法是創建自己的IEqualityComparer<Expression>,它可以爲任何表達式創建一個哈希碼,並可以比較兩個表達式是否相等。我可能會用ExpressionVisitor這樣做,但這樣做會很乏味。

0

這是怎麼回事?

public class KeyGeneratorVisitor : ExpressionVisitor 
{ 
    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     return Expression.Parameter(node.Type, node.Type.Name); 
    } 

    protected override Expression VisitMember(MemberExpression node) 
    { 
     if (CanBeEvaluated(node)) 
     { 
      return Expression.Constant(Evaluate(node)); 
     } 
     else 
     { 
      return base.VisitMember(node); 
     } 
    } 

    private static bool CanBeEvaluated(MemberExpression exp) 
    { 
     while (exp.Expression.NodeType == ExpressionType.MemberAccess) 
     { 
      exp = (MemberExpression) exp.Expression; 
     } 

     return (exp.Expression.NodeType == ExpressionType.Constant); 
    } 

    private static object Evaluate(Expression exp) 
    { 
     if (exp.NodeType == ExpressionType.Constant) 
     { 
      return ((ConstantExpression) exp).Value; 
     } 
     else 
     { 
      MemberExpression mexp = (MemberExpression) exp; 
      object value = Evaluate(mexp.Expression); 

      FieldInfo field = mexp.Member as FieldInfo; 
      if (field != null) 
      { 
       return field.GetValue(value); 
      } 
      else 
      { 
       PropertyInfo property = (PropertyInfo) mexp.Member; 
       return property.GetValue(value, null); 
      } 
     } 
    } 
} 

這會將複雜常量表達式替換爲它們的原始值以及參數名稱與它們的類型名稱。因此,只需創建一個新的KeyGeneratorVisitor實例並用您的表達式調用其VisitVisitAndConvert方法。

請注意Expression.ToString方法也將在您的複雜類型上被調用,因此覆蓋ToString方法或在Evaluate方法中爲它們編寫自定義邏輯。

3

我一直在試圖找出一種方案,這種方法可能是有用的,而不會導致瘋狂難以維護的臃腫緩存。

我知道這並不直接回答你的問題,但我想,以提高對這種做法的是,在第一,聽起來很誘人幾個問題:

  • 你是如何計劃管理參數排序? IE瀏覽器。 (x => x.blah ==「slug」& &!x。已刪除)緩存密鑰應該等於(x =>!x .Deleted & & x.blah ==「slug」)緩存密鑰。
  • 您是如何計劃避免緩存中重複的對象的? IE瀏覽器。設計時,多個查詢中的同一個場將與每個查詢分開緩存。說,對於農場出現的每一個slu,,我們都有一個單獨的農場副本。
  • 用更多的參數擴展上面的內容,比如parcel,farmer等會導致更多的匹配查詢,每個查詢都有一個單獨的緩存的副本。這同樣適用於您可能查詢的每種類型,並且參數可能不是同一順序
  • 現在,如果更新服務器場會發生什麼情況?不知道哪些緩存查詢將包含您的場,您將被迫殺死整個緩存。哪種對你想要達到的目標產生反作用。

我可以看到這種方法背後的推理。 0維護性能層。但是,如果不考慮以上幾點,這種方法首先會殺死性能,然後導致很多維護它的嘗試,然後被證明是完全不可維護的。

我一直在那條路上。最終浪費了很多時間並放棄了。

我發現一個更好的方法,當結果來自後端,每個類型的擴展方法單獨或通過一個公共接口分別緩存每個結果實體。

然後,您可以爲您的lambda表達式構建擴展方法,以便在點擊db之前首先嚐試緩存。

var query = (x => x.Crops.Any(y => slug == y.Slug) && x.Deleted == false); 
var results = query.FromCache(); 
if (!results.Any()) { 
    results = query.FromDatabase(); 
    results.ForEach(x = x.ToCache()); 
} 

當然,你仍然需要跟蹤哪些查詢竟然打了數據庫,以避免查詢從緩存中有一個匹配的農場返回3場從DB滿足查詢b在數據庫中實際上有20個配套養殖場可用。所以,每個查詢都需要至少打一次DB。

而且您需要跟蹤返回0個結果的查詢,以避免它們因此觸及數據庫。

但所有的一切,你少了很多代碼,並作爲獎金脫身,當您更新一個農場,你可以

var farm = (f => f.farmId == farmId).FromCache().First(); 
farm.Name = "My Test Farm"; 
var updatedFarm = farm.ToDatabase(); 
updatedFarm.ToCache(); 
+0

我實際上敲了一些東西[這裏]緩存鍵是從部分評估lambda表達式創建的,因此只有IQueryable的返回被緩存。在更新時,緩存將清除用特定鍵緩存的所有對象。這仍然是一項正在進行的工作,但似乎非常有希望。我需要做一些整理重新objectcontext與dbcontext。 (https://github.com/JimBobSquarePants/EFBootstrap) – 2012-11-19 12:26:35

0

如何:

var call = expression.Body as MethodCallExpression; 

if (call != null) 
{ 

    List<object> list = new List<object>(); 

    foreach (Expression argument in call.Arguments) 
    { 

     object o = Expression.Lambda(argument, expression.Parameters).Compile().DynamicInvoke(); 

     list.Add(o); 

    } 

    StringBuilder keyValue = new StringBuilder(); 

    keyValue.Append(expression.Body.ToString()); 

    list.ForEach(e => keyValue.Append(String.Format("_{0}", e.ToString()))); 

    string key = keyValue.ToString(); 

}