├── .gitignore
├── EF.Interception.sln
├── EF.Interception.sln.DotSettings
├── LICENSE
├── NuGet.config
├── README.md
├── appveyor.yml
├── build.ps1
├── global.json
├── src
└── EF.Interception
│ ├── Context.cs
│ ├── EF.Interception.xproj
│ ├── EntityEntry.cs
│ ├── IContext.cs
│ ├── IEntityEntry.cs
│ ├── IHideObjectMembers.cs
│ ├── IInterceptor.cs
│ ├── IValidationResult.cs
│ ├── InterceptionDbContext.cs
│ ├── Interceptor.cs
│ ├── InterceptorMethod.cs
│ ├── InterceptorMethodAttribute.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── ValidationResult.cs
│ └── project.json
└── test
└── EF.Interception.Tests
├── App.config
├── EF.Interception.Tests.xproj
├── InterceptionDbContextTests.cs
├── InterceptorTests.cs
├── TestObjects
├── AuditInterceptor.cs
├── Book.cs
├── IAuditedEntity.cs
├── IEntity.cs
├── IPostExecutedEntity.cs
├── ISoftDeletedEntity.cs
├── InMemoryDbContext.cs
├── MockInterceptor.cs
├── PostExecuteInterceptor.cs
└── SoftDeleteInterceptor.cs
└── project.json
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 |
84 | # Visual Studio profiler
85 | *.psess
86 | *.vsp
87 | *.vspx
88 | *.sap
89 |
90 | # TFS 2012 Local Workspace
91 | $tf/
92 |
93 | # Guidance Automation Toolkit
94 | *.gpState
95 |
96 | # ReSharper is a .NET coding add-in
97 | _ReSharper*/
98 | *.[Rr]e[Ss]harper
99 | *.DotSettings.user
100 |
101 | # JustCode is a .NET coding add-in
102 | .JustCode
103 |
104 | # TeamCity is a build add-in
105 | _TeamCity*
106 |
107 | # DotCover is a Code Coverage Tool
108 | *.dotCover
109 |
110 | # NCrunch
111 | _NCrunch_*
112 | .*crunch*.local.xml
113 | nCrunchTemp_*
114 |
115 | # MightyMoose
116 | *.mm.*
117 | AutoTest.Net/
118 |
119 | # Web workbench (sass)
120 | .sass-cache/
121 |
122 | # Installshield output folder
123 | [Ee]xpress/
124 |
125 | # DocProject is a documentation generator add-in
126 | DocProject/buildhelp/
127 | DocProject/Help/*.HxT
128 | DocProject/Help/*.HxC
129 | DocProject/Help/*.hhc
130 | DocProject/Help/*.hhk
131 | DocProject/Help/*.hhp
132 | DocProject/Help/Html2
133 | DocProject/Help/html
134 |
135 | # Click-Once directory
136 | publish/
137 |
138 | # Publish Web Output
139 | *.[Pp]ublish.xml
140 | *.azurePubxml
141 | # TODO: Comment the next line if you want to checkin your web deploy settings
142 | # but database connection strings (with potential passwords) will be unencrypted
143 | *.pubxml
144 | *.publishproj
145 |
146 | # NuGet Packages
147 | *.nupkg
148 | # The packages folder can be ignored because of Package Restore
149 | **/packages/*
150 | # except build/, which is used as an MSBuild target.
151 | !**/packages/build/
152 | # Uncomment if necessary however generally it will be regenerated when needed
153 | #!**/packages/repositories.config
154 | # NuGet v3's project.json files produces more ignoreable files
155 | *.nuget.props
156 | *.nuget.targets
157 |
158 | # Microsoft Azure Build Output
159 | csx/
160 | *.build.csdef
161 |
162 | # Microsoft Azure Emulator
163 | ecf/
164 | rcf/
165 |
166 | # Microsoft Azure ApplicationInsights config file
167 | ApplicationInsights.config
168 |
169 | # Windows Store app package directory
170 | AppPackages/
171 | BundleArtifacts/
172 |
173 | # Visual Studio cache files
174 | # files ending in .cache can be ignored
175 | *.[Cc]ache
176 | # but keep track of directories ending in .cache
177 | !*.[Cc]ache/
178 |
179 | # Others
180 | ClientBin/
181 | ~$*
182 | *~
183 | *.dbmdl
184 | *.dbproj.schemaview
185 | *.pfx
186 | *.publishsettings
187 | node_modules/
188 | orleans.codegen.cs
189 |
190 | # RIA/Silverlight projects
191 | Generated_Code/
192 |
193 | # Backup & report files from converting an old project file
194 | # to a newer Visual Studio version. Backup files are not needed,
195 | # because we have git ;-)
196 | _UpgradeReport_Files/
197 | Backup*/
198 | UpgradeLog*.XML
199 | UpgradeLog*.htm
200 |
201 | # SQL Server files
202 | *.mdf
203 | *.ldf
204 |
205 | # Business Intelligence projects
206 | *.rdl.data
207 | *.bim.layout
208 | *.bim_*.settings
209 |
210 | # Microsoft Fakes
211 | FakesAssemblies/
212 |
213 | # GhostDoc plugin setting file
214 | *.GhostDoc.xml
215 |
216 | # Node.js Tools for Visual Studio
217 | .ntvs_analysis.dat
218 |
219 | # Visual Studio 6 build log
220 | *.plg
221 |
222 | # Visual Studio 6 workspace options file
223 | *.opt
224 |
225 | # Visual Studio LightSwitch build output
226 | **/*.HTMLClient/GeneratedArtifacts
227 | **/*.DesktopClient/GeneratedArtifacts
228 | **/*.DesktopClient/ModelManifest.xml
229 | **/*.Server/GeneratedArtifacts
230 | **/*.Server/ModelManifest.xml
231 | _Pvt_Extensions
232 |
233 | # Paket dependency manager
234 | .paket/paket.exe
235 |
236 | # FAKE - F# Make
237 | .fake/
238 |
--------------------------------------------------------------------------------
/EF.Interception.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.24720.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{905E3683-12E8-47D5-AF85-C030A9C622C8}"
7 | EndProject
8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EF.Interception", "src\EF.Interception\EF.Interception.xproj", "{CA797BD5-418F-4487-BF74-FF4D3DEB63AD}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A38E5873-893F-4AC7-A494-A93483584A87}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{29382C99-3E61-4294-9627-2D577A6353C2}"
13 | ProjectSection(SolutionItems) = preProject
14 | global.json = global.json
15 | NuGet.config = NuGet.config
16 | EndProjectSection
17 | EndProject
18 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EF.Interception.Tests", "test\EF.Interception.Tests\EF.Interception.Tests.xproj", "{06B64112-B93B-4964-B81F-7F508011CA6E}"
19 | EndProject
20 | Global
21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
22 | Debug|Any CPU = Debug|Any CPU
23 | Release|Any CPU = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
26 | {CA797BD5-418F-4487-BF74-FF4D3DEB63AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {CA797BD5-418F-4487-BF74-FF4D3DEB63AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {CA797BD5-418F-4487-BF74-FF4D3DEB63AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {CA797BD5-418F-4487-BF74-FF4D3DEB63AD}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {06B64112-B93B-4964-B81F-7F508011CA6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {06B64112-B93B-4964-B81F-7F508011CA6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {06B64112-B93B-4964-B81F-7F508011CA6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {06B64112-B93B-4964-B81F-7F508011CA6E}.Release|Any CPU.Build.0 = Release|Any CPU
34 | EndGlobalSection
35 | GlobalSection(SolutionProperties) = preSolution
36 | HideSolutionNode = FALSE
37 | EndGlobalSection
38 | GlobalSection(NestedProjects) = preSolution
39 | {CA797BD5-418F-4487-BF74-FF4D3DEB63AD} = {905E3683-12E8-47D5-AF85-C030A9C622C8}
40 | {06B64112-B93B-4964-B81F-7F508011CA6E} = {A38E5873-893F-4AC7-A494-A93483584A87}
41 | EndGlobalSection
42 | EndGlobal
43 |
--------------------------------------------------------------------------------
/EF.Interception.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | <data><IncludeFilters /><ExcludeFilters /></data>
3 | <data />
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "[]" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright [yyyy] [name of copyright owner]
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EF.Interception [](https://ci.appveyor.com/project/khellang/ef-interception)
2 |
3 | EF.Interception is small library that lets you intercept saving of entities in Entity Framework.
4 |
5 | ## Intallation
6 |
7 | Install the plugin from NuGet: `Install-Package EF.Interception`.
8 |
9 | ## Usage
10 |
11 | To enable interception, derive your database context from `InterceptionDbContext` instead of `DbContext`:
12 |
13 | ```csharp
14 | public class MyDbContext : InterceptionDbContext
15 | {
16 | // -- SNIP --
17 | }
18 | ```
19 |
20 | You can then add interceptors by calling `AddInterceptor`. An interceptor must derive from `Interceptor`:
21 |
22 | ```csharp
23 | public class AuditInterceptor : Interceptor
24 | {
25 | public override void PreInsert(IContext context)
26 | {
27 | context.Entity.CreatedAt = DateTime.UtcNow;
28 | context.Entity.ModifiedAt = DateTime.UtcNow;
29 | }
30 |
31 | public override void PreUpdate(IContext context)
32 | {
33 | context.Entity.ModifiedAt = DateTime.UtcNow;
34 | }
35 | }
36 |
37 | public class MyDbContext : InterceptionDbContext
38 | {
39 | public MyDbContext()
40 | {
41 | AddInterceptor(new AuditInterceptor());
42 | }
43 | }
44 | ```
45 |
46 | `Interceptor` has six methods you can override:
47 | - `PreInsert`
48 | - `PreUpdate`
49 | - `PreDelete`
50 | - `PostInsert`
51 | - `PostUpdate`
52 | - `PostDelete`
53 |
54 | All methods takes an `IContext` which has three properties:
55 | - `Entity` - The entity which is intercepted.
56 | - `State` - The entity's current state. This can be altered.
57 | - `ValidationResult` - The entity's validation result.
58 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | os: Visual Studio 2015
2 |
3 | version: 0.2.0-ci000{build}
4 | configuration: Release
5 | cache: C:\Users\appveyor\.dnx\packages
6 |
7 | nuget:
8 | disable_publish_on_pr: true
9 |
10 | pull_requests:
11 | do_not_increment_build_number: true
12 |
13 | install:
14 | - set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH%
15 |
16 | build_script:
17 | - ps: .\build.ps1
18 |
19 | artifacts:
20 | - path: artifacts\packages\**\*.nupkg
21 | name: MyGet
22 |
23 | build:
24 | verbosity: minimal
25 |
26 | test: off
27 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | function Install-Dnvm
2 | {
3 | & where.exe dnvm 2>&1 | Out-Null
4 | if(($LASTEXITCODE -ne 0) -Or ((Test-Path Env:\APPVEYOR) -eq $true))
5 | {
6 | Write-Host "DNVM not found"
7 | &{$Branch='dev';iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/aspnet/Home/dev/dnvminstall.ps1'))}
8 |
9 | # Normally this happens automatically during install but AppVeyor has
10 | # an issue where you may need to manually re-run setup from within this process.
11 | if($env:DNX_HOME -eq $NULL)
12 | {
13 | Write-Host "Initial DNVM environment setup failed; running manual setup"
14 | $tempDnvmPath = Join-Path $env:TEMP "dnvminstall"
15 | $dnvmSetupCmdPath = Join-Path $tempDnvmPath "dnvm.ps1"
16 | & $dnvmSetupCmdPath setup
17 | }
18 | }
19 | }
20 |
21 | function Get-DnxVersion
22 | {
23 | $globalJson = join-path $PSScriptRoot "global.json"
24 | $jsonData = Get-Content -Path $globalJson -Raw | ConvertFrom-JSON
25 | return $jsonData.sdk.version
26 | }
27 |
28 | function Restore-Packages
29 | {
30 | param([string] $DirectoryName)
31 | & dnu restore ("""" + $DirectoryName + """")
32 | }
33 |
34 | function Build-Projects
35 | {
36 | param([string] $DirectoryName, [string] $Configuration)
37 | & dnu build ("""" + $DirectoryName + """") --configuration $Configuration --out .\artifacts\bin; if($LASTEXITCODE -ne 0) { exit 1 }
38 | }
39 |
40 | function Pack-Projects
41 | {
42 | param([string] $DirectoryName, [string] $Configuration)
43 | & dnu pack ("""" + $DirectoryName + """") --configuration $Configuration --out .\artifacts\packages; if($LASTEXITCODE -ne 0) { exit 2 }
44 | }
45 |
46 | function Test-Projects
47 | {
48 | param([string] $DirectoryName)
49 | & dnx -p ("""" + $DirectoryName + """") test; if($LASTEXITCODE -ne 0) { exit 3 }
50 | }
51 |
52 | function Remove-PathVariable
53 | {
54 | param([string] $VariableToRemove)
55 | $path = [Environment]::GetEnvironmentVariable("PATH", "User")
56 | $newItems = $path.Split(';') | Where-Object { $_.ToString() -inotlike $VariableToRemove }
57 | [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join(';', $newItems), "User")
58 | $path = [Environment]::GetEnvironmentVariable("PATH", "Process")
59 | $newItems = $path.Split(';') | Where-Object { $_.ToString() -inotlike $VariableToRemove }
60 | [Environment]::SetEnvironmentVariable("PATH", [System.String]::Join(';', $newItems), "Process")
61 | }
62 |
63 | ########################
64 | # THE BUILD!
65 | ########################
66 |
67 | Push-Location $PSScriptRoot
68 |
69 | $dnxVersion = Get-DnxVersion
70 |
71 | if (!$env:CONFIGURATION)
72 | {
73 | $env:CONFIGURATION = "Release"
74 | }
75 |
76 | # Clean
77 | if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse }
78 |
79 | # Remove the installed DNVM from the path and force use of
80 | # per-user DNVM (which we can upgrade as needed without admin permissions)
81 | Remove-PathVariable "*Program Files\Microsoft DNX\DNVM*"
82 |
83 | # Make sure per-user DNVM is installed
84 | Install-Dnvm
85 |
86 | # Install DNX
87 | dnvm install $dnxVersion -r CoreCLR -NoNative
88 | dnvm install $dnxVersion -r CLR -NoNative
89 |
90 | dnvm use $dnxVersion -r CLR
91 |
92 | # Package restore
93 | Get-ChildItem -Path . -Filter *.xproj -Recurse | ForEach-Object { Restore-Packages $_.DirectoryName }
94 |
95 | # Set build number
96 | $env:DNX_BUILD_VERSION = @{ $true = $env:APPVEYOR_BUILD_NUMBER; $false = 1 }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
97 | Write-Host "Build number: " $env:DNX_BUILD_VERSION
98 |
99 | # Package
100 | Get-ChildItem -Path .\src -Filter *.xproj -Recurse | ForEach-Object { Pack-Projects $_.DirectoryName $env:CONFIGURATION }
101 |
102 | # Test - Skipping tests until properly migrated to new xUnit
103 | Get-ChildItem -Path .\test -Filter *.xproj -Recurse | ForEach-Object { Test-Projects $_.DirectoryName }
104 |
105 | # Switch to Core CLR
106 | # dnvm use $dnxVersion -r CoreCLR
107 |
108 | # Test again
109 | Get-ChildItem -Path .\test -Filter *.xproj -Recurse | ForEach-Object { Test-Projects $_.DirectoryName }
110 |
111 | Pop-Location
112 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "projects": [ "src", "test" ],
3 | "sdk": {
4 | "version": "1.0.0-rc1-final"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/EF.Interception/Context.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Entity;
2 |
3 | namespace EF.Interception
4 | {
5 | internal class Context : IContext
6 | {
7 | public Context(IEntityEntry entry)
8 | {
9 | Entry = entry;
10 | }
11 |
12 | private IEntityEntry Entry { get; }
13 |
14 | public T Entity => (T) Entry.Entity;
15 |
16 | public IValidationResult ValidationResult => new ValidationResult(Entry.ValidationResult);
17 |
18 | public EntityState State
19 | {
20 | get { return Entry.State; }
21 | set { Entry.State = value; }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/EF.Interception/EF.Interception.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | ca797bd5-418f-4487-bf74-ff4d3deb63ad
11 | EF.Interception
12 | ..\..\artifacts\obj\$(MSBuildProjectName)
13 | ..\..\artifacts\bin\$(MSBuildProjectName)\
14 |
15 |
16 |
17 | 2.0
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/EF.Interception/EntityEntry.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Entity;
2 | using System.Data.Entity.Infrastructure;
3 | using System.Data.Entity.Validation;
4 |
5 | namespace EF.Interception
6 | {
7 | internal class EntityEntry : IEntityEntry
8 | {
9 | public EntityEntry(DbEntityEntry entry, EntityState beforeState)
10 | {
11 | Entry = entry;
12 | BeforeState = beforeState;
13 | }
14 |
15 | private DbEntityEntry Entry { get; }
16 |
17 | public EntityState BeforeState { get; }
18 |
19 | public object Entity => Entry.Entity;
20 |
21 | public DbEntityValidationResult ValidationResult => Entry.GetValidationResult();
22 |
23 | public EntityState State
24 | {
25 | get { return Entry.State; }
26 | set { Entry.State = value; }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/EF.Interception/IContext.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Entity;
2 |
3 | namespace EF.Interception
4 | {
5 | ///
6 | /// The interception context.
7 | ///
8 | /// The type of entities to intercept.
9 | public interface IContext : IHideObjectMembers
10 | {
11 | ///
12 | /// Gets the entity that's being intercepted.
13 | ///
14 | /// The entity.
15 | TEntity Entity { get; }
16 |
17 | ///
18 | /// Gets or sets the entity's state.
19 | ///
20 | /// The state.
21 | EntityState State { get; set; }
22 |
23 | ///
24 | /// Gets the validation result.
25 | ///
26 | /// The validation result.
27 | IValidationResult ValidationResult { get; }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/EF.Interception/IEntityEntry.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Entity;
2 | using System.Data.Entity.Validation;
3 |
4 | namespace EF.Interception
5 | {
6 | ///
7 | /// Describes the entity entry from the DbContext.
8 | ///
9 | public interface IEntityEntry : IHideObjectMembers
10 | {
11 | ///
12 | /// Gets the entity object.
13 | ///
14 | /// The entity object.
15 | object Entity { get; }
16 |
17 | ///
18 | /// Gets or sets the entity's state.
19 | ///
20 | /// The entity's state.
21 | EntityState State { get; set; }
22 |
23 | ///
24 | /// Gets the entity's state before saving changes in context.
25 | ///
26 | EntityState BeforeState { get; }
27 |
28 | ///
29 | /// Gets the entity's validation result.
30 | ///
31 | /// The entity's validation result.
32 | DbEntityValidationResult ValidationResult { get; }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/EF.Interception/IHideObjectMembers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 |
4 | namespace EF.Interception
5 | {
6 | [EditorBrowsable(EditorBrowsableState.Never)]
7 | public interface IHideObjectMembers
8 | {
9 | [EditorBrowsable(EditorBrowsableState.Never)]
10 | Type GetType();
11 |
12 | [EditorBrowsable(EditorBrowsableState.Never)]
13 | string ToString();
14 |
15 | [EditorBrowsable(EditorBrowsableState.Never)]
16 | bool Equals(object obj);
17 |
18 | [EditorBrowsable(EditorBrowsableState.Never)]
19 | int GetHashCode();
20 | }
21 | }
--------------------------------------------------------------------------------
/src/EF.Interception/IInterceptor.cs:
--------------------------------------------------------------------------------
1 | namespace EF.Interception
2 | {
3 | internal interface IInterceptor
4 | {
5 | void Intercept(IEntityEntry entityEntry, bool isPostSave);
6 | }
7 |
8 | internal interface IInterceptor : IInterceptor where TEntity : class
9 | {
10 | void PreInsert(IContext context);
11 |
12 | void PreUpdate(IContext context);
13 |
14 | void PreDelete(IContext context);
15 |
16 | void PostInsert(IContext context);
17 |
18 | void PostUpdate(IContext context);
19 |
20 | void PostDelete(IContext context);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/EF.Interception/IValidationResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Data.Entity.Validation;
3 |
4 | namespace EF.Interception
5 | {
6 | ///
7 | /// Validation result for an entity.
8 | ///
9 | public interface IValidationResult : IHideObjectMembers
10 | {
11 | ///
12 | /// Gets the errors, if any.
13 | ///
14 | /// The errors, if any.
15 | IEnumerable Errors { get; }
16 |
17 | ///
18 | /// Gets a value indicating whether the entity is valid.
19 | ///
20 | /// true if the entity is valid; otherwise, false.
21 | bool IsValid { get; }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/EF.Interception/InterceptionDbContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Data.Common;
4 | using System.Data.Entity;
5 | using System.Data.Entity.Core.Objects;
6 | using System.Data.Entity.Infrastructure;
7 | using System.Linq;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 |
11 | namespace EF.Interception
12 | {
13 | ///
14 | /// A DbContext with support for intercepting entities before and after they're saved.
15 | ///
16 | public abstract class InterceptionDbContext : DbContext
17 | {
18 | private readonly List _interceptors = new List();
19 |
20 | private readonly object _lock = new object();
21 |
22 | protected InterceptionDbContext() { }
23 |
24 | protected InterceptionDbContext(DbCompiledModel model)
25 | : base(model)
26 | { }
27 |
28 | protected InterceptionDbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection)
29 | : base(existingConnection, model, contextOwnsConnection)
30 | { }
31 |
32 | protected InterceptionDbContext(DbConnection existingConnection, bool contextOwnsConnection)
33 | : base(existingConnection, contextOwnsConnection)
34 | { }
35 |
36 | protected InterceptionDbContext(ObjectContext objectContext, bool dbContextOwnsObjectContext)
37 | : base(objectContext, dbContextOwnsObjectContext)
38 | { }
39 |
40 | protected InterceptionDbContext(string nameOrConnectionString)
41 | : base(nameOrConnectionString)
42 | { }
43 |
44 | protected InterceptionDbContext(string nameOrConnectionString, DbCompiledModel model)
45 | : base(nameOrConnectionString, model)
46 | { }
47 |
48 | ///
49 | /// Saves all changes made in this context to the underlying database.
50 | ///
51 | /// The number of objects written to the underlying database.
52 | public override int SaveChanges()
53 | {
54 | var modifiedEntries = ChangeTracker.Entries()
55 | .Where(IsChanged)
56 | .Select(entry => new EntityEntry(entry, entry.State))
57 | .ToList();
58 |
59 | Intercept(modifiedEntries, false);
60 |
61 | var result = base.SaveChanges();
62 |
63 | Intercept(modifiedEntries, true);
64 |
65 | return result;
66 | }
67 |
68 | public override async Task SaveChangesAsync(CancellationToken cancellationToken)
69 | {
70 | var modifiedEntries = ChangeTracker.Entries()
71 | .Where(IsChanged)
72 | .Select(entry => new EntityEntry(entry, entry.State))
73 | .ToList();
74 |
75 | Intercept(modifiedEntries, false);
76 |
77 | var result = await base.SaveChangesAsync(cancellationToken);
78 |
79 | Intercept(modifiedEntries, true);
80 |
81 | return result;
82 | }
83 |
84 | ///
85 | /// Adds the given interceptor to the context.
86 | ///
87 | /// The type of entities to intercept.
88 | /// The interceptor.
89 | /// If the interceptor is null
90 | public void AddInterceptor(Interceptor interceptor) where TEntity : class
91 | {
92 | if (interceptor == null)
93 | {
94 | throw new ArgumentNullException(nameof(interceptor));
95 | }
96 |
97 | lock (_lock)
98 | {
99 | _interceptors.Add(interceptor);
100 | }
101 | }
102 |
103 | private static bool IsChanged(DbEntityEntry entry)
104 | {
105 | return entry.State != EntityState.Unchanged && entry.State != EntityState.Detached;
106 | }
107 |
108 | private void Intercept(IList entityEntries, bool isPostSave)
109 | {
110 | lock (_lock)
111 | {
112 | foreach (var interceptor in _interceptors)
113 | {
114 | foreach (var entityEntry in entityEntries)
115 | {
116 | interceptor.Intercept(entityEntry, isPostSave);
117 | }
118 | }
119 | }
120 | }
121 | }
122 | }
--------------------------------------------------------------------------------
/src/EF.Interception/Interceptor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Data.Entity;
4 | using System.Linq;
5 |
6 | namespace EF.Interception
7 | {
8 | ///
9 | /// Convenience base class for interceptors.
10 | ///
11 | /// The type of entities to intercept.
12 | public abstract class Interceptor : IInterceptor where TEntity : class
13 | {
14 | private readonly IEnumerable> _methods;
15 |
16 | protected Interceptor()
17 | {
18 | _methods = GetInterceptorMethods();
19 | }
20 |
21 | ///
22 | /// Called before the entity is inserted.
23 | ///
24 | /// The context.
25 | [InterceptorMethod(EntityState.Added, false)]
26 | public virtual void PreInsert(IContext context) { }
27 |
28 | ///
29 | /// Called before the entity is updated.
30 | ///
31 | /// The context.
32 | [InterceptorMethod(EntityState.Modified, false)]
33 | public virtual void PreUpdate(IContext context) { }
34 |
35 | ///
36 | /// Called before the entity is deleted.
37 | ///
38 | /// The context.
39 | [InterceptorMethod(EntityState.Deleted, false)]
40 | public virtual void PreDelete(IContext context) { }
41 |
42 | ///
43 | /// Called after the entity is inserted.
44 | ///
45 | /// The context.
46 | [InterceptorMethod(EntityState.Added, true)]
47 | public virtual void PostInsert(IContext context) { }
48 |
49 | ///
50 | /// Called after the entity is updated.
51 | ///
52 | /// The context.
53 | [InterceptorMethod(EntityState.Modified, true)]
54 | public virtual void PostUpdate(IContext context) { }
55 |
56 | ///
57 | /// Called after the entity is deleted.
58 | ///
59 | /// The context.
60 | [InterceptorMethod(EntityState.Deleted, true)]
61 | public virtual void PostDelete(IContext context) { }
62 |
63 | public void Intercept(IEntityEntry entityEntry, bool isPostSave)
64 | {
65 | if (entityEntry == null)
66 | {
67 | throw new ArgumentNullException(nameof(entityEntry));
68 | }
69 |
70 | if (!(entityEntry.Entity is TEntity))
71 | {
72 | return;
73 | }
74 |
75 | foreach (var method in _methods.Where(m => m.CanIntercept(entityEntry.BeforeState, isPostSave)))
76 | {
77 | method.Invoke(this, entityEntry);
78 | }
79 | }
80 |
81 | private IEnumerable> GetInterceptorMethods()
82 | {
83 | var targetType = GetType();
84 |
85 | var interfaceType = typeof(IInterceptor<>).MakeGenericType(typeof(TEntity));
86 |
87 | return targetType.GetInterfaceMap(interfaceType).TargetMethods
88 | .Where(x => x.DeclaringType == targetType)
89 | .Select(method => new InterceptorMethod(method));
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/src/EF.Interception/InterceptorMethod.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Entity;
3 | using System.Reflection;
4 |
5 | namespace EF.Interception
6 | {
7 | internal class InterceptorMethod where TEntity : class
8 | {
9 | private readonly MethodInfo _method;
10 |
11 | private readonly EntityState _state;
12 |
13 | private readonly bool _isAfterSave;
14 |
15 | private readonly Type _contextType;
16 |
17 | public InterceptorMethod(MethodInfo method)
18 | {
19 | var attribute = method.GetCustomAttribute(true);
20 | if (attribute == null)
21 | {
22 | throw new InvalidOperationException(
23 | "Interceptor methods must be marked with an InterceptorMethodAttribute.");
24 | }
25 |
26 | _method = method;
27 | _state = attribute.State;
28 | _isAfterSave = attribute.IsPostSave;
29 | _contextType = typeof(Context<>).MakeGenericType(typeof(TEntity));
30 | }
31 |
32 | public bool CanIntercept(EntityState state, bool isPostSave)
33 | {
34 | return _state == state && _isAfterSave == isPostSave;
35 | }
36 |
37 | public void Invoke(Interceptor target, IEntityEntry entityEntry)
38 | {
39 | _method.Invoke(target, new[] { Activator.CreateInstance(_contextType, entityEntry) });
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/src/EF.Interception/InterceptorMethodAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Entity;
3 |
4 | namespace EF.Interception
5 | {
6 | [AttributeUsage(AttributeTargets.Method)]
7 | internal class InterceptorMethodAttribute : Attribute
8 | {
9 | public InterceptorMethodAttribute(EntityState state, bool isPostSave)
10 | {
11 | State = state;
12 | IsPostSave = isPostSave;
13 | }
14 |
15 | public EntityState State { get; private set; }
16 |
17 | public bool IsPostSave { get; private set; }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/EF.Interception/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("EF.Interception")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Kristian Hellang")]
12 | [assembly: AssemblyProduct("EF.Interception")]
13 | [assembly: AssemblyCopyright("Copyright © Kristian Hellang 2013")]
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("0851777f-eb64-4488-b0b2-c18eb753b20d")]
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.1.0.0")]
36 | [assembly: AssemblyFileVersion("0.1.0.0")]
37 | [assembly: AssemblyInformationalVersion("0.1.0")]
38 |
39 | [assembly: InternalsVisibleTo("EF.Interception.Tests")]
40 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
--------------------------------------------------------------------------------
/src/EF.Interception/ValidationResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Data.Entity.Validation;
3 |
4 | namespace EF.Interception
5 | {
6 | internal class ValidationResult : IValidationResult
7 | {
8 | public ValidationResult(DbEntityValidationResult result)
9 | {
10 | Result = result;
11 | }
12 |
13 | private DbEntityValidationResult Result { get; }
14 |
15 | public IEnumerable Errors => Result.ValidationErrors;
16 |
17 | public bool IsValid => Result.IsValid;
18 | }
19 | }
--------------------------------------------------------------------------------
/src/EF.Interception/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0-*",
3 | "title": "Entity Framework Interception",
4 | "description": "A library for intercepting EntityFramework actions, like insert, update and delete.",
5 | "authors": [ "Kristian Hellang" ],
6 | "tags": [ "Entity", "Framework", "Interception" ],
7 |
8 | "projectUrl": "https://github.com/khellang/EF.Interception",
9 | "licenseUrl": "https://raw.github.com/khellang/EF.Interception/master/LICENSE",
10 |
11 | "dependencies": {
12 | "EntityFramework": "6.1.3"
13 | },
14 |
15 | "frameworks": {
16 | "net451": {
17 | "frameworkAssemblies": {
18 | "System.Data": "4.0.0.0"
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/EF.Interception.Tests.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | 06b64112-b93b-4964-b81f-7f508011ca6e
11 | EF.Interception.Tests
12 | ..\..\artifacts\obj\$(MSBuildProjectName)
13 | ..\..\artifacts\bin\$(MSBuildProjectName)\
14 |
15 |
16 |
17 | 2.0
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/InterceptionDbContextTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Entity.Validation;
3 |
4 | using Xunit;
5 |
6 | namespace EF.Interception.Tests
7 | {
8 | public class InterceptionDbContextTests
9 | {
10 | public class SaveChanges : IDisposable
11 | {
12 | private readonly InMemoryDbContext _context;
13 |
14 | public SaveChanges()
15 | {
16 | _context = new InMemoryDbContext();
17 | }
18 |
19 | [Fact]
20 | public void ShouldThrowOnInvalidEntities()
21 | {
22 | _context.Books.Add(new Book { Name = "Harry Potter and the Philosopher's Stone" });
23 |
24 | Assert.Throws(() => _context.SaveChanges());
25 | }
26 |
27 | [Fact]
28 | public void ShouldNotThrowOnInvalidEntitiesWhenInterceptorSetsRequiredFields()
29 | {
30 | _context.AddInterceptor(new AuditInterceptor());
31 | _context.Books.Add(new Book { Name = "Harry Potter and the Chamber of Secrets" });
32 |
33 | _context.SaveChanges();
34 | }
35 |
36 | [Fact]
37 | public void ShouldNotDeleteEntitiesWhenStateIsSetToModifiedDuringInterception()
38 | {
39 | _context.AddInterceptor(new SoftDeleteInterceptor());
40 |
41 | var book = new Book
42 | {
43 | Name = "Harry Potter and the Prisoner of Azkaban",
44 | CreatedAt = DateTime.Now, // Need these for validation...
45 | ModifiedAt = DateTime.Now
46 | };
47 |
48 | _context.Books.Add(book);
49 | _context.SaveChanges();
50 |
51 | _context.Books.Remove(book);
52 | _context.SaveChanges();
53 |
54 | var otherBook = _context.Books.Find(book.Id);
55 |
56 | Assert.Same(book, otherBook);
57 | Assert.True(book.IsDeleted);
58 | }
59 |
60 | [Fact]
61 | public void ShouldExecutePostMethodWhenEntityIsAdded()
62 | {
63 | _context.AddInterceptor(new PostExecuteInterceptor());
64 |
65 | var book = new Book
66 | {
67 | Name = "Harry Potter and the Goblet of Fire",
68 | CreatedAt = DateTime.Now, // Need these for validation...
69 | ModifiedAt = DateTime.Now
70 | };
71 |
72 | _context.Books.Add(book);
73 | _context.SaveChanges();
74 |
75 | Assert.True(book.IsPostExecuted);
76 | }
77 |
78 | public void Dispose()
79 | {
80 | _context.Dispose();
81 | }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/InterceptorTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Entity;
3 | using System.Linq.Expressions;
4 | using Moq;
5 | using Xunit;
6 |
7 | namespace EF.Interception.Tests
8 | {
9 | public class InterceptorTests
10 | {
11 | public class Intercept
12 | {
13 | [Fact]
14 | public void ShouldCallPreInsertWhenEntityIsInserted()
15 | {
16 | DoTest(EntityState.Added, false, x =>
17 | x.PreInsert(It.Is>(y => y.State == EntityState.Added)));
18 | }
19 |
20 | [Fact]
21 | public void ShouldCallPreUpdateWhenEntityIsUpdated()
22 | {
23 | DoTest(EntityState.Modified, false, x =>
24 | x.PreUpdate(It.Is>(y => y.State == EntityState.Modified)));
25 | }
26 |
27 | [Fact]
28 | public void ShouldCallPreDeleteWhenEntityIsDeleted()
29 | {
30 | DoTest(EntityState.Deleted, false, x =>
31 | x.PreDelete(It.Is>(y => y.State == EntityState.Deleted)));
32 | }
33 |
34 | [Fact]
35 | public void ShouldCallPostInsertAfterEntityWasInserted()
36 | {
37 | DoTest(EntityState.Added, true, x =>
38 | x.PostInsert(It.Is>(y => y.State == EntityState.Added)));
39 | }
40 |
41 | [Fact]
42 | public void ShouldCallPostUpdateAfterEntityWasUpdated()
43 | {
44 | DoTest(EntityState.Modified, true, x =>
45 | x.PostUpdate(It.Is>(y => y.State == EntityState.Modified)));
46 | }
47 |
48 | [Fact]
49 | public void ShouldCallPostDeleteAfterEntityWasUpdated()
50 | {
51 | DoTest(EntityState.Deleted, true, x =>
52 | x.PostDelete(It.Is>(y => y.State == EntityState.Deleted)));
53 | }
54 |
55 | [Fact]
56 | public void ShouldNotCallAnythingIfEntityWasDetached()
57 | {
58 | DoTest(EntityState.Detached, true);
59 | }
60 |
61 | [Fact]
62 | public void ShouldNotCallAnythingIfEntityIsUnchanged()
63 | {
64 | DoTest(EntityState.Unchanged, true);
65 | }
66 |
67 | private static void DoTest(
68 | EntityState state,
69 | bool isPostSave,
70 | Expression>> expression = null)
71 | {
72 | // Arrange
73 | var entityEntry = new Mock();
74 | entityEntry.SetupGet(x => x.Entity).Returns(new Book { Id = 123 });
75 | entityEntry.SetupGet(x => x.State).Returns(state);
76 | entityEntry.SetupGet(x => x.BeforeState).Returns(state);
77 |
78 | var interceptor = new Mock>(MockBehavior.Strict);
79 | if (expression != null) interceptor.Setup(expression);
80 |
81 | // Act
82 | new MockInterceptor(interceptor.Object).Intercept(entityEntry.Object, isPostSave);
83 |
84 | // Assert
85 | interceptor.VerifyAll();
86 | }
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/AuditInterceptor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EF.Interception.Tests
4 | {
5 | public class AuditInterceptor : Interceptor
6 | {
7 | public override void PreInsert(IContext context)
8 | {
9 | context.Entity.ModifiedAt = DateTime.Now;
10 | context.Entity.CreatedAt = DateTime.Now;
11 | }
12 |
13 | public override void PreUpdate(IContext context)
14 | {
15 | context.Entity.ModifiedAt = DateTime.Now;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/Book.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 |
4 | namespace EF.Interception.Tests
5 | {
6 | public class Book : IAuditedEntity, ISoftDeletedEntity, IPostExecutedEntity
7 | {
8 | public int Id { get; set; }
9 |
10 | [Required]
11 | public string Name { get; set; }
12 |
13 | [Required]
14 | public DateTime? CreatedAt { get; set; }
15 |
16 | [Required]
17 | public DateTime? ModifiedAt { get; set; }
18 |
19 | public bool IsDeleted { get; set; }
20 |
21 | public bool IsPostExecuted { get; set; }
22 | }
23 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/IAuditedEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace EF.Interception.Tests
4 | {
5 | public interface IAuditedEntity : IEntity
6 | {
7 | DateTime? CreatedAt { get; set; }
8 |
9 | DateTime? ModifiedAt { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/IEntity.cs:
--------------------------------------------------------------------------------
1 | namespace EF.Interception.Tests
2 | {
3 | public interface IEntity
4 | {
5 | int Id { get; set; }
6 | }
7 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/IPostExecutedEntity.cs:
--------------------------------------------------------------------------------
1 | namespace EF.Interception.Tests
2 | {
3 | public interface IPostExecutedEntity : IEntity
4 | {
5 | bool IsPostExecuted { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/ISoftDeletedEntity.cs:
--------------------------------------------------------------------------------
1 | namespace EF.Interception.Tests
2 | {
3 | public interface ISoftDeletedEntity : IEntity
4 | {
5 | bool IsDeleted { get; set; }
6 | }
7 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/InMemoryDbContext.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Entity;
2 |
3 | namespace EF.Interception.Tests
4 | {
5 | public class InMemoryDbContext : InterceptionDbContext
6 | {
7 | public InMemoryDbContext() : base(Effort.DbConnectionFactory.CreateTransient(), true) { }
8 |
9 | public DbSet Books { get; set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/MockInterceptor.cs:
--------------------------------------------------------------------------------
1 | namespace EF.Interception.Tests
2 | {
3 | internal class MockInterceptor : Interceptor where TEntity : class
4 | {
5 | private readonly IInterceptor _interceptor;
6 |
7 | public MockInterceptor(IInterceptor interceptor)
8 | {
9 | _interceptor = interceptor;
10 | }
11 |
12 | public override void PreInsert(IContext context)
13 | {
14 | _interceptor.PreInsert(context);
15 | }
16 |
17 | public override void PreUpdate(IContext context)
18 | {
19 | _interceptor.PreUpdate(context);
20 | }
21 |
22 | public override void PreDelete(IContext context)
23 | {
24 | _interceptor.PreDelete(context);
25 | }
26 |
27 | public override void PostInsert(IContext context)
28 | {
29 | _interceptor.PostInsert(context);
30 | }
31 |
32 | public override void PostUpdate(IContext context)
33 | {
34 | _interceptor.PostUpdate(context);
35 | }
36 |
37 | public override void PostDelete(IContext context)
38 | {
39 | _interceptor.PostDelete(context);
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/PostExecuteInterceptor.cs:
--------------------------------------------------------------------------------
1 | namespace EF.Interception.Tests
2 | {
3 | public class PostExecuteInterceptor : Interceptor
4 | {
5 | public override void PostInsert(IContext context)
6 | {
7 | context.Entity.IsPostExecuted = true;
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/TestObjects/SoftDeleteInterceptor.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Entity;
2 |
3 | namespace EF.Interception.Tests
4 | {
5 | public class SoftDeleteInterceptor : Interceptor
6 | {
7 | public override void PreDelete(IContext context)
8 | {
9 | context.Entity.IsDeleted = true;
10 | context.State = EntityState.Modified;
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/test/EF.Interception.Tests/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "EF.Interception": { "target": "project" },
4 | "Effort.EF6": "1.1.4",
5 | "Moq": "4.2.1510.2205",
6 | "xunit": "2.1.0",
7 | "xunit.runner.dnx": "2.1.0-rc1-build204"
8 | },
9 |
10 | "commands": {
11 | "test": "xunit.runner.dnx"
12 | },
13 |
14 | "frameworks": {
15 | "dnx451": {
16 | "frameworkAssemblies": {
17 | "System.Data": "4.0.0.0"
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------