2012-03-17 34 views
10

Tôi đã xem xét các câu hỏi khác tương tự câu hỏi này nhưng tôi không thể tìm thấy bất kỳ câu trả lời khả thi nào.Chuyển đổi biểu thức lambda thành khóa duy nhất để lưu vào bộ nhớ đệm

Tôi đã sử dụng mã sau để tạo khóa duy nhất để lưu trữ kết quả truy vấn LINQ của tôi vào bộ nhớ cache.

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; 

Dường như hoạt động tốt cho các truy vấn đơn giản chứa số nguyên hoặc boolean nhưng khi truy vấn của tôi chứa các biểu thức liên tục lồng nhau, ví dụ:

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

Mấu chốt lại là như sau:

(Đúng AndAlso (Farm.Crops.Any (y => (giá trị (OzFarmGuide.Controllers.FarmController + <> c__DisplayClassd) .slug == y.Slug)) AndAlso (Farm.Deleted == False)))

Như bạn có thể thấy bất kỳ tên cây trồng nào tôi vượt qua cũng sẽ cho kết quả tương tự. Có cách nào tôi có thể trích xuất các giá trị của các tham số nhất định để tôi có thể phân biệt giữa các truy vấn của tôi?

Cũng chuyển đổi y nói tên đúng loại sẽ được tốt đẹp .....

+0

whats sai với việc sử dụng phương thức 'GetHashCode()' và 'HashSet '? Nó không phải là duy nhất nhưng một 'HashSet' là trong hầu hết trường hợp có thể nhận và thêm các mục trong O (1). –

+0

@CommuSoft, điều đó sẽ không hoạt động, bởi vì ngay cả hai biểu thức trông giống hệt nhau sẽ không được coi là bằng nhau (trừ khi bạn cung cấp trình so sánh bình đẳng của riêng bạn). – svick

+0

@CommuSoft - Bên cạnh đó, mã băm không được đảm bảo duy nhất và do đó có thể có lỗi trong mã của bạn – Polity

Trả lời

5

Như Chính thể và Marc nói trong ý kiến ​​của họ, những gì bạn cần là một đánh giá một phần của biểu thức LINQ. Bạn có thể đọc cách thực hiện điều đó bằng cách sử dụng ExpressionVisitor trong Matt Warren's LINQ: Building an IQueryable Provider - Part III. Bài viết Caching the results of LINQ queries by Pete Montgomery (được liên kết với bởi Polity) mô tả một số chi tiết cụ thể hơn về loại bộ nhớ đệm này, ví dụ: cách trình bày các bộ sưu tập trong truy vấn.

Ngoài ra, tôi không chắc mình sẽ dựa vào ToString() như thế này. Tôi nghĩ rằng nó có nghĩa là chủ yếu cho mục đích gỡ lỗi và nó có thể thay đổi trong tương lai. Cách khác sẽ tạo ra IEqualityComparer<Expression> của riêng bạn, có thể tạo mã băm cho bất kỳ biểu thức nào và có thể so sánh hai biểu thức cho sự bình đẳng. Tôi có lẽ sẽ làm điều đó bằng cách sử dụng ExpressionVisitor quá, nhưng làm như vậy sẽ khá tẻ nhạt.

0

Điều này thì sao?

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

Điều này sẽ thay thế biểu thức hằng số phức tạp thành giá trị ban đầu cũng như tên thông số cho tên loại của chúng. Vì vậy, chỉ cần tạo một phiên bản KeyGeneratorVisitor mới và gọi phương thức Visit hoặc VisitAndConvert của nó với biểu thức của bạn.

Xin lưu ý rằng phương pháp Expression.ToString cũng sẽ được gọi trên các loại phức tạp của bạn, do đó, hãy ghi đè phương thức ToString hoặc viết logic tùy chỉnh cho chúng theo phương pháp Evaluate.

3

Tôi đã cố gắng tìm ra một kịch bản mà cách tiếp cận này có thể hữu ích mà không dẫn đến bộ nhớ cache cồng kềnh khó khăn để duy trì.

Tôi biết điều này là không trực tiếp trả lời các câu hỏi của bạn, nhưng tôi muốn nêu lên một số câu hỏi về phương pháp này mà, lúc đầu, nghe có vẻ hấp dẫn:

  • Làm thế nào mà bạn có kế hoạch để quản lý thông số đặt hàng? I E. (x => x.blah == "slug" & &! x.Đã xóa) khóa bộ nhớ cache phải bằng (x =>! X.Deleted & & x.blah == "slug") khóa bộ nhớ cache.
  • Bạn dự định tránh các đối tượng trùng lặp trong bộ nhớ cache như thế nào? I E. Cùng một trang trại từ nhiều truy vấn sẽ theo thiết kế được lưu trữ riêng biệt với mỗi truy vấn. Giả sử, đối với mỗi con sên xuất hiện trong trang trại, chúng tôi có một bản sao riêng của trang trại.
  • Mở rộng ở trên với nhiều thông số hơn, chẳng hạn như bưu kiện, nông dân, v.v. sẽ dẫn đến các truy vấn phù hợp hơn với mỗi truy vấn có một bản sao riêng biệt của trang trại được lưu trong bộ nhớ cache. Điều tương tự cũng áp dụng cho từng loại bạn có thể truy vấn cộng với các tham số có thể không theo cùng thứ tự
  • Bây giờ, điều gì sẽ xảy ra nếu bạn cập nhật trang trại? Nếu không biết các truy vấn được lưu trong bộ nhớ cache nào sẽ chứa trang trại của bạn, bạn sẽ bị buộc phải xóa toàn bộ bộ nhớ cache của mình. Loại phản tác dụng với những gì bạn đang cố gắng đạt được.

Tôi có thể thấy lý do đằng sau phương pháp này. Lớp hiệu suất bảo trì 0. Tuy nhiên, nếu các điểm trên không được xem xét, cách tiếp cận đầu tiên sẽ giết hiệu suất, sau đó dẫn đến rất nhiều nỗ lực để duy trì nó, sau đó chứng minh là hoàn toàn không thể duy trì.

Tôi đã đi xuống con đường đó. Cuối cùng lãng phí rất nhiều thời gian và bỏ cuộc.

Tôi tìm thấy một cách tiếp cận tốt hơn nhiều bằng cách lưu vào bộ nhớ cache mỗi thực thể riêng biệt khi kết quả đến từ phần phụ trợ với phương pháp mở rộng cho từng loại riêng biệt hoặc thông qua giao diện chung.

Sau đó, bạn có thể xây dựng phương pháp mở rộng cho các biểu thức lambda của bạn trước tiên hãy thử bộ nhớ cache trước khi nhấn 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()); 
} 

Tất nhiên, bạn vẫn sẽ cần phải theo dõi truy vấn đã thực sự nhấn cơ sở dữ liệu để tránh truy vấn Một trở về 3 trang trại từ DB đáp ứng truy vấn B với một khớp trại từ bộ nhớ cache trong khi cơ sở dữ liệu sẽ thực sự có 20 trang trại phù hợp có sẵn. Vì vậy, mỗi truy vấn stll cần phải nhấn DB ít nhất một lần.

Và bạn cần phải theo dõi các truy vấn trả lại 0 kết quả để tránh chúng do đó đánh DB không có gì.

Nhưng tất cả trong tất cả, bạn nhận được ngay với mã ít hơn rất nhiều và như một phần thưởng, khi bạn cập nhật một trang trại, bạn có thể

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

Tôi thực sự gõ một cái gì đó [ở đây] Các phím bộ nhớ cache được tạo ra từ một phần đánh giá biểu thức lambda vì vậy chỉ có sự trở lại từ IQueryable được lưu trữ. Khi cập nhật bộ nhớ cache sẽ xóa tất cả các đối tượng được lưu trong bộ nhớ cache bằng một khóa cụ thể. Nó vẫn là một công việc đang tiến triển nhưng có vẻ rất hứa hẹn. Tôi cần phải làm một số gọn gàng lên objectcontext vs dbcontext. (https://github.com/JimBobSquarePants/EFBootstrap) –

0

Làm thế nào về:

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

} 
Các vấn đề liên quan