├── .gitignore ├── .nuget ├── NuGet.exe ├── NuGet.settings.targets └── NuGet.targets ├── LICENSE.md ├── QueryInterceptor.sln ├── QueryInterceptor ├── Properties │ └── AssemblyInfo.cs ├── QueryInterceptor.csproj ├── QueryTranslator.cs ├── QueryTranslatorProvider.cs └── QueryableExtensions.cs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | *.user 4 | /TestResults 5 | *.vspscc 6 | *.vssscc 7 | *.suo 8 | *.cache 9 | *.docstates 10 | *.dgml 11 | _ReSharper.* 12 | *.csproj.user 13 | *[Rr]e[Ss]harper.user 14 | _ReSharper.*/ 15 | packages/* -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidfowl/QueryInterceptor/d39837c7b750508eac07404375186ffc42ec5478/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/NuGet.settings.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | $(SolutionDir).nuget 6 | $(NuGetToolsPath)\nuget.exe 7 | $(ProjectDir)packages.config 8 | $(SolutionDir)packages 9 | $(TargetDir.Trim('\\')) 10 | 11 | 12 | "" 13 | 14 | 15 | false 16 | 17 | 18 | false 19 | 20 | 21 | "$(NuGetExePath)" install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)" 22 | "$(NuGetExePath)" pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols 23 | 24 | 25 | 26 | RestorePackages; 27 | $(BuildDependsOn); 28 | 29 | 30 | 31 | 32 | $(BuildDependsOn); 33 | BuildPackage; 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011 David Fowler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /QueryInterceptor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QueryInterceptor", "QueryInterceptor\QueryInterceptor.csproj", "{C8991ABA-C5B5-4782-8B63-D47D699DA84A}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {C8991ABA-C5B5-4782-8B63-D47D699DA84A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {C8991ABA-C5B5-4782-8B63-D47D699DA84A}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {C8991ABA-C5B5-4782-8B63-D47D699DA84A}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {C8991ABA-C5B5-4782-8B63-D47D699DA84A}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /QueryInterceptor/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("QueryInterceptor")] 9 | [assembly: AssemblyDescription("Exposes a generic extension method to IQueryable that allows interception of expression trees with expression visitors.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("David Fowler")] 12 | [assembly: AssemblyProduct("QueryInterceptor")] 13 | [assembly: AssemblyCopyright("Copyright © David Fowler 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("12def7c2-a196-458e-8928-321e4543e303")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.2.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /QueryInterceptor/QueryInterceptor.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {C8991ABA-C5B5-4782-8B63-D47D699DA84A} 9 | Library 10 | Properties 11 | QueryInterceptor 12 | QueryInterceptor 13 | v4.0 14 | 512 15 | ..\ 16 | true 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | -------------------------------------------------------------------------------- /QueryInterceptor/QueryTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | 7 | namespace QueryInterceptor 8 | { 9 | internal class QueryTranslator : IOrderedQueryable 10 | { 11 | private readonly Expression _expression; 12 | private readonly QueryTranslatorProvider _provider; 13 | 14 | public QueryTranslator(IQueryable source, IEnumerable visitors) 15 | { 16 | if (source == null) 17 | { 18 | throw new ArgumentNullException("source"); 19 | } 20 | 21 | if (visitors == null) 22 | { 23 | throw new ArgumentNullException("visitors"); 24 | } 25 | 26 | _expression = Expression.Constant(this); 27 | _provider = new QueryTranslatorProvider(source, visitors); 28 | } 29 | 30 | public QueryTranslator(IQueryable source, Expression expression, IEnumerable visitors) 31 | { 32 | if (expression == null) 33 | { 34 | throw new ArgumentNullException("expression"); 35 | } 36 | _expression = expression; 37 | _provider = new QueryTranslatorProvider(source, visitors); 38 | } 39 | 40 | public IEnumerator GetEnumerator() 41 | { 42 | return ((IEnumerable)_provider.ExecuteEnumerable(_expression)).GetEnumerator(); 43 | } 44 | 45 | IEnumerator IEnumerable.GetEnumerator() 46 | { 47 | return _provider.ExecuteEnumerable(_expression).GetEnumerator(); 48 | } 49 | 50 | public Type ElementType 51 | { 52 | get { return typeof(T); } 53 | } 54 | 55 | public Expression Expression 56 | { 57 | get { return _expression; } 58 | } 59 | 60 | public IQueryProvider Provider 61 | { 62 | get { return _provider; } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /QueryInterceptor/QueryTranslatorProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | 7 | namespace QueryInterceptor 8 | { 9 | internal abstract class QueryTranslatorProvider : ExpressionVisitor 10 | { 11 | private readonly IQueryable _source; 12 | 13 | protected QueryTranslatorProvider(IQueryable source) 14 | { 15 | if (source == null) 16 | { 17 | throw new ArgumentNullException("source"); 18 | } 19 | 20 | _source = source; 21 | } 22 | 23 | internal IQueryable Source 24 | { 25 | get { return _source; } 26 | } 27 | } 28 | 29 | internal class QueryTranslatorProvider : QueryTranslatorProvider, IQueryProvider 30 | { 31 | private readonly IEnumerable _visitors; 32 | 33 | public QueryTranslatorProvider(IQueryable source, IEnumerable visitors) 34 | : base(source) 35 | { 36 | if (visitors == null) 37 | { 38 | throw new ArgumentNullException("visitors"); 39 | } 40 | _visitors = visitors; 41 | } 42 | 43 | public IQueryable CreateQuery(Expression expression) 44 | { 45 | if (expression == null) 46 | { 47 | throw new ArgumentNullException("expression"); 48 | } 49 | 50 | return new QueryTranslator(Source, expression, _visitors) as IQueryable; 51 | } 52 | 53 | public IQueryable CreateQuery(Expression expression) 54 | { 55 | if (expression == null) 56 | { 57 | throw new ArgumentNullException("expression"); 58 | } 59 | 60 | Type elementType = expression.Type.GetGenericArguments().First(); 61 | IQueryable result = (IQueryable)Activator.CreateInstance(typeof(QueryTranslator<>).MakeGenericType(elementType), 62 | new object[] { Source, expression, _visitors }); 63 | return result; 64 | } 65 | 66 | public TResult Execute(Expression expression) 67 | { 68 | if (expression == null) 69 | { 70 | throw new ArgumentNullException("expression"); 71 | } 72 | object result = (this as IQueryProvider).Execute(expression); 73 | return (TResult)result; 74 | } 75 | 76 | public object Execute(Expression expression) 77 | { 78 | if (expression == null) 79 | { 80 | throw new ArgumentNullException("expression"); 81 | } 82 | 83 | Expression translated = VisitAll(expression); 84 | return Source.Provider.Execute(translated); 85 | } 86 | 87 | internal IEnumerable ExecuteEnumerable(Expression expression) 88 | { 89 | if (expression == null) 90 | { 91 | throw new ArgumentNullException("expression"); 92 | } 93 | 94 | Expression translated = VisitAll(expression); 95 | return Source.Provider.CreateQuery(translated); 96 | } 97 | 98 | private Expression VisitAll(Expression expression) 99 | { 100 | // Run all visitors in order 101 | var visitors = new ExpressionVisitor[] { this }.Concat(_visitors); 102 | 103 | return visitors.Aggregate(expression, (expr, visitor) => visitor.Visit(expr)); 104 | } 105 | 106 | protected override Expression VisitConstant(ConstantExpression node) 107 | { 108 | // Fix up the Expression tree to work with the underlying LINQ provider 109 | if (node.Type.IsGenericType && 110 | node.Type.GetGenericTypeDefinition() == typeof(QueryTranslator<>)) 111 | { 112 | 113 | var provider = ((IQueryable)node.Value).Provider as QueryTranslatorProvider; 114 | 115 | if (provider != null) 116 | { 117 | return provider.Source.Expression; 118 | } 119 | 120 | return Source.Expression; 121 | } 122 | 123 | return base.VisitConstant(node); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /QueryInterceptor/QueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | 5 | namespace QueryInterceptor 6 | { 7 | public static class QueryableExtensions 8 | { 9 | public static IQueryable InterceptWith(this IQueryable source, params ExpressionVisitor[] visitors) 10 | { 11 | if (source == null) 12 | { 13 | throw new ArgumentNullException("source"); 14 | } 15 | return new QueryTranslator(source, visitors); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The problem 2 | Normally when you're trying to modify expression trees you need to write an expression visitor. 3 | When trying to plug your expression visitor into an IQuerable's expression tree, you need to write a linq provider (yikes). 4 | 5 | Implementation based on 6 | 7 | ## The solution 8 | QueryInterceptor introduces one extension method on IQueryable (InterceptWith) that lets you plug in arbitrary expression visitors. 9 | 10 | ```C# 11 | namespace QueryInterceptor { 12 | public static class QueryableExtensions { 13 | public static IQueryable InterceptWith(this IQueryable source, params ExpressionVisitor[] visitors); 14 | } 15 | } 16 | ``` 17 | 18 | ## Basic Example 19 | The example below uses an expression visitor that changes == to != anywhere in the expression tree. 20 | ```C# 21 | public class EqualsToNotEqualsVisitor : ExpressionVisitor { 22 | protected override Expression VisitBinary(BinaryExpression node) { 23 | if (node.NodeType == ExpressionType.Equal) { 24 | // Change == to != 25 | return Expression.NotEqual(node.Left, node.Right); 26 | } 27 | return base.VisitBinary(node); 28 | } 29 | } 30 | 31 | class Program { 32 | static void Main(string[] args) { 33 | var query = from n in Enumerable.Range(0, 10).AsQueryable() 34 | where n % 2 == 0 35 | select n; 36 | 37 | // Print even numbers 38 | foreach (var item in query) { 39 | Console.WriteLine(item); 40 | } 41 | 42 | Console.WriteLine(); 43 | 44 | // Print odd numbers 45 | var visitor = new EqualsToNotEqualsVisitor(); 46 | foreach (var item in query.InterceptWith(visitor)) { 47 | Console.WriteLine(item); 48 | } 49 | } 50 | } 51 | ``` 52 | --------------------------------------------------------------------------------