From e8e08e3eef669a35487dfd81020c2a9abb1bcf26 Mon Sep 17 00:00:00 2001 From: cocowalla Date: Tue, 21 Oct 2014 11:36:29 +0100 Subject: [PATCH 1/3] Fix for #133. Compatibility for custom `IDbSet<>` Fix for #133. Adds compatibility for custom `IDbSet<>` implementations by looking for an `ObjectQuery` property after trying the existing checks --- .../Extensions/ObjectQueryExtensions.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs b/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs index 66a7c31..73d143c 100644 --- a/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs @@ -28,8 +28,18 @@ public static ObjectQuery ToObjectQuery(this IQueryable; + + // next look for an ObjectQuery property if (dbQuery == null) - return null; + { + var queryType = query.GetType(); + var prop = queryType.GetProperties().FirstOrDefault(p => p.PropertyType == typeof(ObjectQuery)); + + if (prop == null) + return null; + + return (ObjectQuery)prop.GetValue(query); + } // access internal property InternalQuery dynamic dbQueryProxy = new DynamicProxy(dbQuery); From d16661ba7ab572268996a89148c2113a17985ada Mon Sep 17 00:00:00 2001 From: cocowalla Date: Tue, 21 Oct 2014 12:54:47 +0100 Subject: [PATCH 2/3] Added fix for missing overload in .NET 4.0 --- .../Extensions/ObjectQueryExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs b/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs index 73d143c..0c1032b 100644 --- a/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs +++ b/Source/EntityFramework.Extended/Extensions/ObjectQueryExtensions.cs @@ -38,7 +38,7 @@ public static ObjectQuery ToObjectQuery(this IQueryable)prop.GetValue(query); + return (ObjectQuery)prop.GetValue(query, null); } // access internal property InternalQuery From d5aa5039e81d9bbaeaf1ecbaab1e41d01277cd00 Mon Sep 17 00:00:00 2001 From: cocowalla Date: Wed, 29 Oct 2014 13:16:08 +0000 Subject: [PATCH 3/3] Fix for #14. Use hash of SQL as cache key Fix for #14. Build cache key from a hash of the SQL that will be executed, along with any parameters and their values. With the previous expression-based method, parameters and their values were *not* being included in the cache key when `Include()` was used. --- .../Caching/Query/Evaluator.cs | 138 --------- .../Caching/Query/LocalCollectionExpander.cs | 82 ------ .../Caching/Query/QueryCache.cs | 38 +-- .../EntityFramework.Extended.net40.csproj | 3 - .../EntityFramework.Extended.net45.csproj | 3 - .../Extensions/ObjectQueryExtensions.cs | 276 ++++++++++-------- 6 files changed, 158 insertions(+), 382 deletions(-) delete mode 100644 Source/EntityFramework.Extended/Caching/Query/Evaluator.cs delete mode 100644 Source/EntityFramework.Extended/Caching/Query/LocalCollectionExpander.cs 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 @@ -