├── .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 |
--------------------------------------------------------------------------------