diff --git a/Source/EntityFramework.Extended/Caching/Query/Evaluator.cs b/Source/EntityFramework.Extended/Caching/Query/Evaluator.cs deleted file mode 100644 index 080f6c5..0000000 --- a/Source/EntityFramework.Extended/Caching/Query/Evaluator.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; - -namespace EntityFramework.Caching -{ - /// - /// Enables the partial evaluation of queries. - /// - /// - /// From http://msdn.microsoft.com/en-us/library/bb546158.aspx - /// Copyright notice http://msdn.microsoft.com/en-gb/cc300389.aspx#O - /// - internal static class Evaluator - { - /// - /// Performs evaluation and replacement of independent sub-trees - /// - /// The root of the expression tree. - /// A function that decides whether a given expression node can be part of the local function. - /// A new tree with sub-trees evaluated and replaced. - public static Expression PartialEval(Expression expression, Func fnCanBeEvaluated) - { - return new SubtreeEvaluator(new Nominator(fnCanBeEvaluated).Nominate(expression)).Eval(expression); - } - - /// - /// Performs evaluation and replacement of independent sub-trees - /// - /// The root of the expression tree. - /// A new tree with sub-trees evaluated and replaced. - public static Expression PartialEval(Expression expression) - { - return PartialEval(expression, Evaluator.CanBeEvaluatedLocally); - } - - private static bool CanBeEvaluatedLocally(Expression expression) - { - return expression.NodeType != ExpressionType.Parameter; - } - - /// - /// Evaluates and replaces sub-trees when first candidate is reached (top-down) - /// - class SubtreeEvaluator : ExpressionVisitor - { - HashSet candidates; - - internal SubtreeEvaluator(HashSet candidates) - { - this.candidates = candidates; - } - - internal Expression Eval(Expression exp) - { - return this.Visit(exp); - } - - public override Expression Visit(Expression exp) - { - if (exp == null) - { - return null; - } - if (this.candidates.Contains(exp)) - { - return this.Evaluate(exp); - } - return base.Visit(exp); - } - - private Expression Evaluate(Expression e) - { - if (e.NodeType == ExpressionType.Constant) - { - return e; - } - LambdaExpression lambda = Expression.Lambda(e); - Delegate fn = lambda.Compile(); - return Expression.Constant(fn.DynamicInvoke(null), e.Type); - } - - protected override Expression VisitMemberInit(MemberInitExpression node) - { - if (node.NewExpression.NodeType == ExpressionType.New) - return node; - - return base.VisitMemberInit(node); - } - } - - /// - /// Performs bottom-up analysis to determine which nodes can possibly - /// be part of an evaluated sub-tree. - /// - class Nominator : ExpressionVisitor - { - Func fnCanBeEvaluated; - HashSet candidates; - bool cannotBeEvaluated; - - internal Nominator(Func fnCanBeEvaluated) - { - this.fnCanBeEvaluated = fnCanBeEvaluated; - } - - internal HashSet Nominate(Expression expression) - { - this.candidates = new HashSet(); - this.Visit(expression); - return this.candidates; - } - - public override Expression Visit(Expression expression) - { - if (expression != null) - { - bool saveCannotBeEvaluated = this.cannotBeEvaluated; - this.cannotBeEvaluated = false; - base.Visit(expression); - if (!this.cannotBeEvaluated) - { - if (this.fnCanBeEvaluated(expression)) - { - this.candidates.Add(expression); - } - else - { - this.cannotBeEvaluated = true; - } - } - this.cannotBeEvaluated |= saveCannotBeEvaluated; - } - return expression; - } - } - } -} \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Caching/Query/LocalCollectionExpander.cs b/Source/EntityFramework.Extended/Caching/Query/LocalCollectionExpander.cs deleted file mode 100644 index d05d7f8..0000000 --- a/Source/EntityFramework.Extended/Caching/Query/LocalCollectionExpander.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; - -namespace EntityFramework.Caching -{ - /// - /// Enables cache key support for local collection values. - /// - internal class LocalCollectionExpander : ExpressionVisitor - { - public static Expression Rewrite(Expression expression) - { - return new LocalCollectionExpander().Visit(expression); - } - - protected override Expression VisitMethodCall(MethodCallExpression node) - { - // pair the method's parameter types with its arguments - var map = node.Method.GetParameters() - .Zip(node.Arguments, (p, a) => new { Param = p.ParameterType, Arg = a }) - .ToLinkedList(); - - // deal with instance methods - var instanceType = node.Object == null ? null : node.Object.Type; - map.AddFirst(new { Param = instanceType, Arg = node.Object }); - - // for any local collection parameters in the method, make a - // replacement argument which will print its elements - var replacements = (from x in map - where x.Param != null && x.Param.IsGenericType - let g = x.Param.GetGenericTypeDefinition() - where g == typeof(IEnumerable<>) || g == typeof(List<>) - where x.Arg.NodeType == ExpressionType.Constant - let elementType = x.Param.GetGenericArguments().Single() - let printer = MakePrinter((ConstantExpression)x.Arg, elementType) - select new { x.Arg, Replacement = printer }).ToList(); - - if (replacements.Any()) - { - var args = map.Select(x => (from r in replacements - where r.Arg == x.Arg - select r.Replacement).SingleOrDefault() ?? x.Arg).ToList(); - - node = node.Update(args.First(), args.Skip(1)); - } - - return base.VisitMethodCall(node); - } - - ConstantExpression MakePrinter(ConstantExpression enumerable, Type elementType) - { - var value = (IEnumerable)enumerable.Value; - var printerType = typeof(Printer<>).MakeGenericType(elementType); - var printer = Activator.CreateInstance(printerType, value); - - return Expression.Constant(printer); - } - - /// - /// Overrides ToString to print each element of a collection. - /// - /// - /// Inherits List in order to support List.Contains instance method as well - /// as standard Enumerable.Contains/Any extension methods. - /// - class Printer : List - { - public Printer(IEnumerable collection) - { - this.AddRange(collection.Cast()); - } - - public override string ToString() - { - return "{" + this.ToConcatenatedString(t => t.ToString(), "|") + "}"; - } - } - } -} \ No newline at end of file diff --git a/Source/EntityFramework.Extended/Caching/Query/QueryCache.cs b/Source/EntityFramework.Extended/Caching/Query/QueryCache.cs index dcb57ea..9d07d81 100644 --- a/Source/EntityFramework.Extended/Caching/Query/QueryCache.cs +++ b/Source/EntityFramework.Extended/Caching/Query/QueryCache.cs @@ -1,6 +1,5 @@ -using System; -using System.Linq; -using System.Linq.Expressions; +using System.Linq; +using EntityFramework.Extensions; namespace EntityFramework.Caching { @@ -20,18 +19,10 @@ public static class QueryCache /// /// The query to get the key from. /// A unique key for the specified - public static string GetCacheKey(this IQueryable query) + public static string GetCacheKey(this IQueryable query) where TEntity : class { - var expression = query.Expression; - - // locally evaluate as much of the query as possible - expression = Evaluator.PartialEval(expression, QueryCache.CanBeEvaluatedLocally); - - // support local collections - expression = LocalCollectionExpander.Rewrite(expression); - - // use the string representation of the expression for the cache key - string key = expression.ToString(); + // The key is made up of the SQL that will be executed, along with any parameters and their values + string key = query.ToTraceString(); // the key is potentially very long, so use an md5 fingerprint // (fine if the query result data isn't critically sensitive) @@ -39,24 +30,5 @@ public static string GetCacheKey(this IQueryable query) return key; } - - static Func CanBeEvaluatedLocally - { - get - { - return expression => - { - // don't evaluate parameters - if (expression.NodeType == ExpressionType.Parameter) - return false; - - // can't evaluate queries - if (typeof(IQueryable).IsAssignableFrom(expression.Type)) - return false; - - return true; - }; - } - } } } diff --git a/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj b/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj index 948e605..6dfa3b0 100644 --- a/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj +++ b/Source/EntityFramework.Extended/EntityFramework.Extended.net40.csproj @@ -86,8 +86,6 @@ - - @@ -152,7 +150,6 @@ -