├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── EntityFrameworkCoreExtensions.sln ├── LICENSE.txt ├── NuGet.config ├── global.json ├── samples └── AutoModelSample │ ├── AutoModelSample.xproj │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── project.json ├── src ├── EntityFrameworkCoreExtensions.SqlServer │ ├── EntityFrameworkCoreExtensions.SqlServer.xproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Storage │ │ └── ExtendedSqlServerTypeMapper.cs │ └── project.json ├── EntityFrameworkCoreExtensions │ ├── ActionExecutor.cs │ ├── Builder │ │ ├── AutoModelAttribute.cs │ │ ├── AutoModelDbContextHook.cs │ │ ├── EntityTypeBuilderBase.cs │ │ ├── IEntityTypeBuilder.cs │ │ ├── IModelBuilder.cs │ │ ├── IModelBuilderService.cs │ │ ├── ModelBuilderBase.cs │ │ └── ModelBuilderService.cs │ ├── ChangeTracking │ │ ├── ChangeDetectorHookBase.cs │ │ ├── ExtendedChangeDetector.cs │ │ └── IChangeDetectorHook.cs │ ├── DbContextHookBase.cs │ ├── DependencyInjection │ │ ├── ExtensionsServicesBuilder.cs │ │ └── ServiceCollectionExtensions.cs │ ├── EntityFrameworkCoreExtensions.xproj │ ├── ExtendedDbContext.cs │ ├── FirstResultFuncExecutor.cs │ ├── IDbContextHook.cs │ ├── Materialization │ │ ├── EntityMaterializerSourceHookBase.cs │ │ ├── ExtendedEntityMaterializerSource.cs │ │ └── IEntityMaterializerSourceHook.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Storage │ │ ├── IRelationalTypeMapperHook.cs │ │ └── RelationalTypeMapperHookBase.cs │ └── project.json └── Shared │ ├── Ensure.cs │ └── TaskCache.cs └── test ├── EntityFrameworkCoreExtensions.SqlServer.Tests ├── EntityFrameworkCoreExtensions.SqlServer.Tests.xproj ├── Properties │ └── AssemblyInfo.cs ├── Storage │ └── ExtendedSqlServerTypeMapperTests.cs └── project.json └── EntityFrameworkCoreExtensions.Tests ├── ActionExecutorTests.cs ├── Builder ├── AutoModelDbContextHookTests.cs └── ModelBuilderServiceTests.cs ├── ChangeTracking └── ExtendedChangeDetectorTests.cs ├── EntityFrameworkCoreExtensions.Tests.xproj ├── ExtendedDbContextTests.cs ├── FirstResultFuncExecutorTests.cs ├── Materialization └── ExtendedEntityMaterializerSourceTests.cs ├── Properties └── AssemblyInfo.cs └── project.json /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Steps to reproduce 2 | Ideally include a complete code listing that we can run to reproduce the issue. 3 | Alternatively, you can provide a project/solution that we can run. 4 | 5 | ### The issue 6 | Describe what is not working as expected. 7 | 8 | If you are seeing an exception, include the full exceptions details (message and stack trace). 9 | 10 | ``` 11 | Exception message: 12 | Stack trace: 13 | ``` 14 | 15 | ### Further technical details 16 | 17 | Entity Framework Core Extensions version: (found in project.json or packages.config) 18 | EF Core version: (found in project.json or packages.config) 19 | Operating system: 20 | Visual Studio version: (e.g. VS 2013 or n/a) 21 | 22 | Other details about my project setup: -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Please check if the PR fulfills these requirements** 2 | 3 | - [ ] The code builds and tests pass (verified by our automated build checks) 4 | - [ ] Commit messages follow this format 5 | ``` 6 | Summary of the changes 7 | - Detail 1 8 | - Detail 2 9 | 10 | Fixes #bugnumber 11 | ``` 12 | - [ ] Tests for the changes have been added (for bug fixes / features) -------------------------------------------------------------------------------- /.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 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 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 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug//", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "externalConsole": false, 13 | "stopAtEntry": false, 14 | "internalConsoleOptions": "openOnSessionStart" 15 | }, 16 | { 17 | "name": ".NET Core Attach", 18 | "type": "coreclr", 19 | "request": "attach", 20 | "processId": "${command.pickProcess}" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [ 10 | "" 11 | ], 12 | "isBuildCommand": true, 13 | "problemMatcher": "$msCompile" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /EntityFrameworkCoreExtensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{82F9EB8C-5411-4A9A-A931-689511E3F4B4}" 7 | EndProject 8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EntityFrameworkCoreExtensions", "src\EntityFrameworkCoreExtensions\EntityFrameworkCoreExtensions.xproj", "{04F3AC4F-E3FD-41C2-84E3-DC934DC276EA}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{3144AA51-6920-42FE-8FCF-515292CCBF7C}" 11 | ProjectSection(SolutionItems) = preProject 12 | src\Shared\Ensure.cs = src\Shared\Ensure.cs 13 | src\Shared\TaskCache.cs = src\Shared\TaskCache.cs 14 | EndProjectSection 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{CFFFD37C-21AA-482E-A31C-4F7E7FB36BD1}" 17 | EndProject 18 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EntityFrameworkCoreExtensions.Tests", "test\EntityFrameworkCoreExtensions.Tests\EntityFrameworkCoreExtensions.Tests.xproj", "{92CB8734-ADB3-4E79-9020-1B09BD235275}" 19 | EndProject 20 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EntityFrameworkCoreExtensions.SqlServer", "src\EntityFrameworkCoreExtensions.SqlServer\EntityFrameworkCoreExtensions.SqlServer.xproj", "{75775E1F-1B75-40F0-A5EF-6F1CFDAAB5EB}" 21 | EndProject 22 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "EntityFrameworkCoreExtensions.SqlServer.Tests", "test\EntityFrameworkCoreExtensions.SqlServer.Tests\EntityFrameworkCoreExtensions.SqlServer.Tests.xproj", "{95A0954B-D79E-4944-9A39-8D7F03EFBB43}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{CAC9BF80-1225-40B5-9003-A4F1118D70A0}" 25 | EndProject 26 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AutoModelSample", "samples\AutoModelSample\AutoModelSample.xproj", "{01FA892D-A79F-42B7-84FB-F85A34A5AD1F}" 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {04F3AC4F-E3FD-41C2-84E3-DC934DC276EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {04F3AC4F-E3FD-41C2-84E3-DC934DC276EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {04F3AC4F-E3FD-41C2-84E3-DC934DC276EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {04F3AC4F-E3FD-41C2-84E3-DC934DC276EA}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {92CB8734-ADB3-4E79-9020-1B09BD235275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {92CB8734-ADB3-4E79-9020-1B09BD235275}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {92CB8734-ADB3-4E79-9020-1B09BD235275}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {92CB8734-ADB3-4E79-9020-1B09BD235275}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {75775E1F-1B75-40F0-A5EF-6F1CFDAAB5EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {75775E1F-1B75-40F0-A5EF-6F1CFDAAB5EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {75775E1F-1B75-40F0-A5EF-6F1CFDAAB5EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {75775E1F-1B75-40F0-A5EF-6F1CFDAAB5EB}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {95A0954B-D79E-4944-9A39-8D7F03EFBB43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {95A0954B-D79E-4944-9A39-8D7F03EFBB43}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {95A0954B-D79E-4944-9A39-8D7F03EFBB43}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {95A0954B-D79E-4944-9A39-8D7F03EFBB43}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {01FA892D-A79F-42B7-84FB-F85A34A5AD1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {01FA892D-A79F-42B7-84FB-F85A34A5AD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {01FA892D-A79F-42B7-84FB-F85A34A5AD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {01FA892D-A79F-42B7-84FB-F85A34A5AD1F}.Release|Any CPU.Build.0 = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(NestedProjects) = preSolution 59 | {04F3AC4F-E3FD-41C2-84E3-DC934DC276EA} = {82F9EB8C-5411-4A9A-A931-689511E3F4B4} 60 | {3144AA51-6920-42FE-8FCF-515292CCBF7C} = {82F9EB8C-5411-4A9A-A931-689511E3F4B4} 61 | {92CB8734-ADB3-4E79-9020-1B09BD235275} = {CFFFD37C-21AA-482E-A31C-4F7E7FB36BD1} 62 | {75775E1F-1B75-40F0-A5EF-6F1CFDAAB5EB} = {82F9EB8C-5411-4A9A-A931-689511E3F4B4} 63 | {95A0954B-D79E-4944-9A39-8D7F03EFBB43} = {CFFFD37C-21AA-482E-A31C-4F7E7FB36BD1} 64 | {01FA892D-A79F-42B7-84FB-F85A34A5AD1F} = {CAC9BF80-1225-40B5-9003-A4F1118D70A0} 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": ["src", "test"] 3 | } -------------------------------------------------------------------------------- /samples/AutoModelSample/AutoModelSample.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 01fa892d-a79f-42b7-84fb-f85a34a5ad1f 11 | AutoModelSample 12 | .\obj 13 | .\bin\ 14 | v4.5 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/AutoModelSample/Program.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace AutoModelSample 4 | { 5 | using System.Reflection; 6 | using System.Collections.Generic; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 9 | using Microsoft.Extensions.DependencyInjection; 10 | 11 | using EntityFrameworkCoreExtensions; 12 | using EntityFrameworkCoreExtensions.Builder; 13 | 14 | public class Program 15 | { 16 | public static void Main(string[] args) 17 | { 18 | var services = new ServiceCollection(); 19 | services 20 | .AddEntityFrameworkInMemoryDatabase() 21 | .AddDbContext(options => options.UseInMemoryDatabase()) 22 | .AddEntityFrameworkCoreExtensions(b => b 23 | .AddModelBuildersFromAssembly(typeof(Program).GetTypeInfo().Assembly) 24 | .AddAutoModel() 25 | ); 26 | var provider = services.BuildServiceProvider(); 27 | 28 | var context = provider.GetService(); 29 | 30 | var product = new Product { Name = "Test Product" }; 31 | product.Variants = new List() 32 | { 33 | new ProductVariant { Name = "Test Product Variant" } 34 | }; 35 | 36 | context.Add(product); 37 | context.SaveChanges(); 38 | } 39 | } 40 | 41 | public class Product 42 | { 43 | public int Id { get; set; } 44 | 45 | public string Name { get; set; } 46 | 47 | public virtual ICollection Variants { get; set; } 48 | } 49 | 50 | public class ProductVariant 51 | { 52 | public int Id { get; set; } 53 | 54 | public string Name { get; set; } 55 | 56 | public int ProductId { get; set; } 57 | 58 | public virtual Product Product { get; set; } 59 | } 60 | 61 | [AutoModel] 62 | public class CatalogDbContext : ExtendedDbContext 63 | { 64 | public CatalogDbContext(IEnumerable hooks, DbContextOptions options) : base(hooks, options) 65 | { 66 | } 67 | 68 | public DbSet Products { get; set; } 69 | 70 | public DbSet Variants { get; set; } 71 | } 72 | 73 | public class ProductEntityTypeBuilder : EntityTypeBuilderBase 74 | { 75 | public override void BuildEntity(EntityTypeBuilder builder) 76 | { 77 | builder.HasKey(p => p.Id); 78 | builder.Property(p => p.Name).IsRequired().HasMaxLength(200); 79 | 80 | builder.HasMany(p => p.Variants) 81 | .WithOne(pv => pv.Product) 82 | .HasPrincipalKey(p => p.Id) 83 | .HasForeignKey(pv => pv.ProductId); 84 | } 85 | } 86 | 87 | public class ProductVariantEntityTypeBuilder : EntityTypeBuilderBase 88 | { 89 | public override void BuildEntity(EntityTypeBuilder builder) 90 | { 91 | builder.HasAlternateKey(pv => pv.Id); 92 | builder.Property(pv => pv.Name).IsRequired().HasMaxLength(200); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /samples/AutoModelSample/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: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("AutoModelSample")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("01fa892d-a79f-42b7-84fb-f85a34a5ad1f")] 20 | -------------------------------------------------------------------------------- /samples/AutoModelSample/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "EntityFrameworkCoreExtensions": { "target": "project" }, 9 | "Microsoft.EntityFrameworkCore.InMemory": "1.0.1" 10 | }, 11 | 12 | "frameworks": { 13 | "netcoreapp1.0": { 14 | "dependencies": { 15 | "Microsoft.NETCore.App": { 16 | "type": "platform", 17 | "version": "1.0.1" 18 | } 19 | } 20 | }, 21 | "net451": {} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions.SqlServer/EntityFrameworkCoreExtensions.SqlServer.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 75775e1f-1b75-40f0-a5ef-6f1cfdaab5eb 10 | EntityFrameworkCoreExtensions 11 | .\obj 12 | .\bin\ 13 | v4.5 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions.SqlServer/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: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("EntityFrameworkCoreExtensions.SqlServer")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("75775e1f-1b75-40f0-a5ef-6f1cfdaab5eb")] 20 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions.SqlServer/Storage/ExtendedSqlServerTypeMapper.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Storage 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | using Microsoft.EntityFrameworkCore.Storage; 9 | using Microsoft.EntityFrameworkCore.Storage.Internal; 10 | 11 | /// 12 | /// An extended Sql Server type mapper with support for hooks. 13 | /// 14 | public class ExtendedSqlServerTypeMapper : SqlServerTypeMapper 15 | { 16 | private readonly FirstResultFuncExecutor _hooks; 17 | 18 | /// 19 | /// Initialises a new instance of . 20 | /// 21 | /// The set of relational type mapper hooks. 22 | public ExtendedSqlServerTypeMapper(IEnumerable hooks) 23 | { 24 | _hooks = new FirstResultFuncExecutor(Ensure.NotNull(hooks.ToArray(), nameof(hooks))); 25 | } 26 | 27 | /// 28 | public override RelationalTypeMapping FindMapping(IProperty property) 29 | { 30 | return _hooks.Execute(r => r.FindMapping(property)) 31 | ?? base.FindMapping(property); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions.SqlServer/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "description": "Provides hooks and extensions for the Entity Framework Core project with SQL Server", 4 | "packOptions": { 5 | "tags": [ 6 | "Entity Framework Core Extensions", 7 | "Entity Framework Core", 8 | "entity-framework-core-extensions", 9 | "entity-framework-core", 10 | "ef-extensions", 11 | "ef", 12 | "Data", 13 | "O/RM", 14 | "SQL Server" 15 | ] 16 | }, 17 | "buildOptions": { 18 | "warningsAsErrors": true, 19 | "xmlDoc": true, 20 | "compile": { 21 | "include": "../Shared/*.cs" 22 | } 23 | }, 24 | "dependencies": { 25 | "EntityFrameworkCoreExtensions": { "target": "project" }, 26 | "Microsoft.EntityFrameworkCore.SqlServer": "1.0.1" 27 | }, 28 | "frameworks": { 29 | "net451": {}, 30 | "netstandard1.3": {} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/ActionExecutor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System; 6 | using System.Linq; 7 | 8 | /// 9 | /// Converts an array enumeration into a delegated action flow. 10 | /// 11 | /// The element type. 12 | public class ActionExecutor 13 | { 14 | private readonly Action> _flow; 15 | 16 | /// 17 | /// Initialises a new instance of 18 | /// 19 | /// The source array. 20 | public ActionExecutor(T[] items) 21 | { 22 | _flow = CreateFlow(Ensure.NotNull(items, nameof(items))); 23 | } 24 | 25 | /// 26 | /// Executes the given action over all items in the source array. 27 | /// 28 | /// The action to execute. 29 | public void Execute(Action action) 30 | => _flow?.Invoke(Ensure.NotNull(action, nameof(action))); 31 | 32 | private static Action> CreateFlow(T[] items) 33 | => items 34 | .Reverse() 35 | .Aggregate((Action>)null, (prev, item) => CreateFlowCore(item, prev)); 36 | 37 | private static Action> CreateFlowCore(T item, Action> prev) 38 | => (act) => 39 | { 40 | act(item); 41 | prev?.Invoke(act); 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Builder/AutoModelAttribute.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Builder 4 | { 5 | using System; 6 | 7 | /// 8 | /// Marks a database context to enable auto-building of the model. 9 | /// 10 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 11 | public class AutoModelAttribute : Attribute 12 | { 13 | /// 14 | /// Gets or sets whether to include navigations when building the model. 15 | /// 16 | public bool IncludeNavigations { get; set; } = true; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Builder/AutoModelDbContextHook.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Builder 4 | { 5 | using System.Reflection; 6 | using Microsoft.EntityFrameworkCore; 7 | 8 | /// 9 | /// Automatically builds the model for a database context using the properties. 10 | /// 11 | public class AutoModelDbContextHook : DbContextHookBase 12 | { 13 | private readonly IModelBuilderService _modelBuilderService; 14 | 15 | /// 16 | /// Initialises a new instance of 17 | /// 18 | /// The model builder service. 19 | public AutoModelDbContextHook(IModelBuilderService modelBuilderService) 20 | { 21 | _modelBuilderService = Ensure.NotNull(modelBuilderService, nameof(modelBuilderService)); 22 | } 23 | 24 | /// 25 | public override void OnModelCreating(ModelBuilder modelBuilder) 26 | { 27 | if (DbContextType == null) 28 | { 29 | return; 30 | } 31 | 32 | var attribute = DbContextType.GetTypeInfo().GetCustomAttribute(true); 33 | if (attribute == null) 34 | { 35 | return; 36 | } 37 | 38 | // MA - Resolve the model builders that can be applied to the model. 39 | var builders = _modelBuilderService.GetModelBuilders(DbContextType, attribute.IncludeNavigations); 40 | 41 | // Apply each builder. 42 | foreach (var builder in builders) 43 | { 44 | builder.BuildModel(modelBuilder); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Builder/EntityTypeBuilderBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Builder 4 | { 5 | using System; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 8 | 9 | /// 10 | /// Provides a base implementation of an entity type builder. 11 | /// 12 | /// The entity type. 13 | public abstract class EntityTypeBuilderBase : ModelBuilderBase, IEntityTypeBuilder 14 | where TEntity : class 15 | { 16 | /// 17 | public override Type EntityType => typeof(TEntity); 18 | 19 | /// 20 | public override bool IsTyped => true; 21 | 22 | /// 23 | public override void BuildModel(ModelBuilder builder) 24 | { 25 | Ensure.NotNull(builder, nameof(builder)); 26 | 27 | BuildEntity(builder.Entity()); 28 | } 29 | 30 | /// 31 | public abstract void BuildEntity(EntityTypeBuilder builder); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Builder/IEntityTypeBuilder.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Builder 4 | { 5 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 6 | 7 | /// 8 | /// Defines the required contract for implementing an entity type builder. 9 | /// 10 | public interface IEntityTypeBuilder : IModelBuilder 11 | where T : class 12 | { 13 | /// 14 | /// Builds the entity model. 15 | /// 16 | /// The entity type builder. 17 | void BuildEntity(EntityTypeBuilder builder); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Builder/IModelBuilder.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Builder 4 | { 5 | using System; 6 | using Microsoft.EntityFrameworkCore; 7 | 8 | /// 9 | /// Defines the required contract for implementing a model builder. 10 | /// 11 | public interface IModelBuilder 12 | { 13 | /// 14 | /// Gets the database context type. 15 | /// 16 | Type DbContextType { get; } 17 | 18 | /// 19 | /// Gets the entity type. 20 | /// 21 | Type EntityType { get; } 22 | 23 | /// 24 | /// Gets whether this is a typed model builder. 25 | /// 26 | bool IsTyped { get; } 27 | 28 | /// 29 | /// Builds the model. 30 | /// 31 | /// The instance. 32 | void BuildModel(ModelBuilder builder); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Builder/IModelBuilderService.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Builder 4 | { 5 | using System; 6 | 7 | /// 8 | /// Defines the required contract for implementing a model builder service. 9 | /// 10 | public interface IModelBuilderService 11 | { 12 | /// 13 | /// Gets the set of model builders for the given database context type. 14 | /// 15 | /// The database context type. 16 | /// Indicates whether navigation entities should be included. 17 | /// The set of model builders. 18 | IModelBuilder[] GetModelBuilders(Type dbContextType, bool includeNavigations); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Builder/ModelBuilderBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Builder 4 | { 5 | using System; 6 | using Microsoft.EntityFrameworkCore; 7 | 8 | /// 9 | /// Provides a base implementation of a model builder. 10 | /// 11 | public abstract class ModelBuilderBase : IModelBuilder 12 | { 13 | /// 14 | public virtual Type DbContextType => null; 15 | 16 | /// 17 | public virtual Type EntityType => null; 18 | 19 | /// 20 | public virtual bool IsTyped => false; 21 | 22 | /// 23 | public abstract void BuildModel(ModelBuilder builder); 24 | } 25 | 26 | /// 27 | /// Provides a base implementation of a model builder. 28 | /// 29 | /// The context type. 30 | public abstract class ModelBuilderBase : ModelBuilderBase 31 | where TContext : DbContext 32 | { 33 | /// 34 | public override Type DbContextType => typeof(TContext); 35 | 36 | /// 37 | public override bool IsTyped => true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Builder/ModelBuilderService.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Builder 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using Microsoft.EntityFrameworkCore; 10 | 11 | /// 12 | /// Provides services for resolving model builders for a database context. 13 | /// 14 | public class ModelBuilderService : IModelBuilderService 15 | { 16 | private static readonly Type[] _supportedCollectionTypes = new[] 17 | { 18 | typeof(List<>), 19 | typeof(IList<>), 20 | typeof(ICollection<>) 21 | }; 22 | 23 | private IModelBuilder[] _builders; 24 | 25 | /// 26 | /// Initialises a new instance of 27 | /// 28 | /// The set of model builders. 29 | public ModelBuilderService(IEnumerable builders) 30 | { 31 | _builders = Ensure.NotNull(builders, nameof(builders)).ToArray(); 32 | } 33 | 34 | /// 35 | public IModelBuilder[] GetModelBuilders(Type dbContextType, bool includeNavigations) 36 | { 37 | Ensure.NotNull(dbContextType, nameof(dbContextType)); 38 | 39 | // MA - Get the untyped builders - these apply to all database contexts. 40 | var untypedBuilders = _builders.Where(b => !b.IsTyped); 41 | // MA - Get the typed builders for compatible database contexts. 42 | var contextTypedBuilders = _builders.Where(b => b.IsTyped && b.DbContextType != null && b.DbContextType.IsAssignableFrom(dbContextType)); 43 | 44 | // MA - Get the entity types from DbSet properties 45 | var setTypes = FindSets(dbContextType); 46 | // MA - Get the typed builders for properties on the database context. 47 | var setTypedBuilders = _builders.Where(b => b.IsTyped && b.EntityType != null && setTypes.Any(t => b.EntityType.Equals(t))); 48 | 49 | IEnumerable navigationTypedBuilders = Enumerable.Empty(); 50 | if (includeNavigations) 51 | { 52 | // MA - Gets the navigations for the given entity types. 53 | var navigationTypes = FindNavigations(setTypes); 54 | // MA - Get the typed builders for navigations. 55 | navigationTypedBuilders = _builders.Where(b => b.IsTyped && b.EntityType != null && navigationTypes.Any(t => b.EntityType.Equals(t))); 56 | } 57 | 58 | return untypedBuilders 59 | .Concat(contextTypedBuilders) 60 | .Concat(setTypedBuilders) 61 | .Concat(navigationTypedBuilders) 62 | .ToArray(); 63 | } 64 | 65 | private Type[] FindNavigations(Type[] setTypes) 66 | { 67 | var knownTypes = new HashSet(setTypes); 68 | var resultTypes = new HashSet(); 69 | 70 | foreach (var setType in setTypes) 71 | { 72 | foreach (var resultType in FindNavigationsCore(setType, knownTypes)) 73 | { 74 | resultTypes.Add(resultType); 75 | } 76 | } 77 | 78 | return resultTypes.ToArray(); 79 | } 80 | 81 | private IEnumerable FindNavigationsCore(Type type, HashSet knownTypes) 82 | { 83 | foreach (var navigationType in FindNavigationCandidates(type)) 84 | { 85 | if (!knownTypes.Contains(navigationType)) 86 | { 87 | knownTypes.Add(navigationType); 88 | 89 | yield return navigationType; 90 | 91 | foreach (var subNavigationType in FindNavigationsCore(navigationType, knownTypes)) 92 | { 93 | yield return subNavigationType; 94 | } 95 | } 96 | } 97 | } 98 | 99 | private Type[] FindNavigationCandidates(Type entityType) 100 | => entityType.GetRuntimeProperties() 101 | .Where( 102 | p => !IsStatic(p) 103 | && !p.PropertyType.GetTypeInfo().IsValueType) 104 | .Select(p => ExtractNavigationType(p.PropertyType)) 105 | .Where(p => p != null) 106 | .ToArray(); 107 | 108 | 109 | private Type[] FindSets(Type dbContextType) 110 | => dbContextType.GetRuntimeProperties() 111 | .Where( 112 | p => !IsStatic(p) 113 | && !p.GetIndexParameters().Any() 114 | && p.DeclaringType != typeof(DbContext) 115 | && p.DeclaringType != typeof(ExtendedDbContext) 116 | && p.PropertyType.GetTypeInfo().IsGenericType 117 | && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)) 118 | .Select(p => p.PropertyType.GetGenericArguments()[0]) 119 | .ToArray(); 120 | 121 | private bool IsStatic(PropertyInfo property) 122 | => (property.GetMethod ?? property.SetMethod).IsStatic; 123 | 124 | private Type ExtractNavigationType(Type propertyType) 125 | { 126 | var typeInfo = propertyType.GetTypeInfo(); 127 | 128 | if (typeInfo.IsValueType 129 | || propertyType == typeof(string)) 130 | { 131 | return null; 132 | } 133 | 134 | if (typeInfo.IsGenericType && IsSupportedCollectionType(typeInfo)) 135 | { 136 | return propertyType.GetGenericArguments()[0]; 137 | } 138 | else if (!typeInfo.IsGenericType) 139 | { 140 | return propertyType; 141 | } 142 | 143 | return null; 144 | } 145 | 146 | private bool IsSupportedCollectionType(TypeInfo typeInfo) 147 | => _supportedCollectionTypes.Any(t => t.Equals(typeInfo.GetGenericTypeDefinition())); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/ChangeTracking/ChangeDetectorHookBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.ChangeTracking 4 | { 5 | using System; 6 | using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; 7 | 8 | /// 9 | /// Provides a base implementation of a . 10 | /// 11 | public abstract class ChangeDetectorHookBase : IChangeDetectorHook 12 | { 13 | /// 14 | public virtual void DetectedChanges(IChangeDetector changeDetector, IStateManager stateManager) 15 | { 16 | } 17 | 18 | /// 19 | public virtual void DetectedEntryChanges(IChangeDetector changeDetector, IStateManager stateManager, InternalEntityEntry entry) 20 | { 21 | } 22 | 23 | /// 24 | public virtual void DetectingChanges(IChangeDetector changeDetector, IStateManager stateManager) 25 | { 26 | } 27 | 28 | /// 29 | public virtual void DetectingEntryChanges(IChangeDetector changeDetector, IStateManager stateManager, InternalEntityEntry entry) 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/ChangeTracking/ExtendedChangeDetector.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.ChangeTracking 4 | { 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; 8 | 9 | /// 10 | /// An extended change detector with support for hooks. 11 | /// 12 | public class ExtendedChangeDetector : ChangeDetector 13 | { 14 | private readonly ActionExecutor _executor; 15 | 16 | /// 17 | /// Initialises a new instance of 18 | /// 19 | /// The set of change detector hooks. 20 | public ExtendedChangeDetector(IEnumerable hooks) 21 | { 22 | _executor = new ActionExecutor(Ensure.NotNull(hooks, nameof(hooks)).ToArray()); 23 | } 24 | 25 | /// 26 | public override void DetectChanges(InternalEntityEntry entry) 27 | { 28 | _executor.Execute(hook => hook.DetectingEntryChanges(this, entry.StateManager, entry)); 29 | 30 | base.DetectChanges(entry); 31 | 32 | _executor.Execute(hook => hook.DetectedEntryChanges(this, entry.StateManager, entry)); 33 | } 34 | 35 | /// 36 | public override void DetectChanges(IStateManager stateManager) 37 | { 38 | _executor.Execute(hook => hook.DetectingChanges(this, stateManager)); 39 | 40 | base.DetectChanges(stateManager); 41 | 42 | _executor.Execute(hook => hook.DetectedChanges(this, stateManager)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/ChangeTracking/IChangeDetectorHook.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.ChangeTracking 4 | { 5 | using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; 6 | 7 | /// 8 | /// Defines the required contract for implementing a change detector hook. 9 | /// 10 | public interface IChangeDetectorHook 11 | { 12 | /// 13 | /// Fired when the change detector is determining changes to tracked entities. 14 | /// 15 | /// The change detector. 16 | /// The state manager. 17 | void DetectingChanges(IChangeDetector changeDetector, IStateManager stateManager); 18 | 19 | /// 20 | /// Fired when the change detector has determined changes to tracked entities. 21 | /// 22 | /// The change detector. 23 | /// The state manager. 24 | void DetectedChanges(IChangeDetector changeDetector, IStateManager stateManager); 25 | 26 | /// 27 | /// Fired when the change detector is determining changes to a tracked entity. 28 | /// 29 | /// The change detector. 30 | /// The state manager. 31 | /// The change tracking entry. 32 | void DetectingEntryChanges(IChangeDetector changeDetector, IStateManager stateManager, InternalEntityEntry entry); 33 | 34 | /// 35 | /// Fired when the change detector has determined changes to a tracked entity. 36 | /// 37 | /// The change detector. 38 | /// The state manager. 39 | /// The change tracking entry. 40 | void DetectedEntryChanges(IChangeDetector changeDetector, IStateManager stateManager, InternalEntityEntry entry); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/DbContextHookBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.EntityFrameworkCore; 9 | using Microsoft.EntityFrameworkCore.ChangeTracking; 10 | 11 | /// 12 | /// Provides a base implementation of a database context hook. 13 | /// 14 | public abstract class DbContextHookBase : IDbContextHook 15 | { 16 | private Type _dbContextType = null; 17 | 18 | /// 19 | public virtual Type DbContextType => _dbContextType; 20 | 21 | /// 22 | public virtual bool IsTyped => false; 23 | 24 | /// 25 | public virtual void AddedEntry(TEntity entity, EntityEntry entry) where TEntity : class 26 | { 27 | } 28 | 29 | /// 30 | public virtual void AddingEntry(TEntity entity) where TEntity : class 31 | { 32 | } 33 | 34 | /// 35 | public virtual void AttachedEntry(TEntity entity, EntityEntry entry) where TEntity : class 36 | { 37 | } 38 | 39 | /// 40 | public virtual void AttachingEntry(TEntity entity) where TEntity : class 41 | { 42 | } 43 | 44 | /// 45 | public virtual void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 46 | { 47 | } 48 | 49 | /// 50 | public virtual void OnModelCreating(ModelBuilder modelBuilder) 51 | { 52 | } 53 | 54 | /// 55 | public virtual void RemovedEntry(TEntity entity, EntityEntry entry) where TEntity : class 56 | { 57 | } 58 | 59 | /// 60 | public virtual void RemovingEntry(TEntity entity) where TEntity : class 61 | { 62 | } 63 | 64 | /// 65 | public virtual void SavedChanges(int persistedStateEntries, bool acceptedAllChangesOnSuccess) 66 | { 67 | } 68 | 69 | /// 70 | public virtual Task SavedChangesAsync(int persistedStateEntries, bool acceptedAllChangesOnSuccess, CancellationToken cancellationToken) 71 | => TaskCache.CompletedTask; 72 | 73 | /// 74 | public virtual void SavingChanges(bool acceptAllChangesOnSuccess) 75 | { 76 | } 77 | 78 | /// 79 | public virtual Task SavingChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken) 80 | => TaskCache.CompletedTask; 81 | 82 | /// 83 | public virtual void UpdatedEntry(TEntity entity, EntityEntry entry) where TEntity : class 84 | { 85 | } 86 | 87 | /// 88 | public virtual void UpdatingEntry(TEntity entity) where TEntity : class 89 | { 90 | } 91 | 92 | /// 93 | void IDbContextHook.SetDbContextType(Type type) 94 | => _dbContextType = Ensure.NotNull(type, nameof(type)); 95 | } 96 | 97 | /// 98 | /// Provides a base implementation of a typed database context hook. 99 | /// 100 | /// The database context type. 101 | public abstract class DbContextHookBase : DbContextHookBase, IDbContextHook 102 | where T : DbContext 103 | { 104 | /// 105 | public override Type DbContextType => typeof(T); 106 | 107 | /// 108 | public override bool IsTyped => true; 109 | 110 | /// 111 | void IDbContextHook.SetDbContextType(Type type) 112 | { } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/DependencyInjection/ExtensionsServicesBuilder.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System.Linq; 6 | using System.Reflection; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | using EntityFrameworkCoreExtensions.Builder; 10 | 11 | /// 12 | /// A builder utility for applying service descriptors to a service collection. 13 | /// 14 | public class ExtensionsServicesBuilder 15 | { 16 | private readonly IServiceCollection _services; 17 | 18 | /// 19 | /// Initialises a new instance of . 20 | /// 21 | /// The services collection. 22 | public ExtensionsServicesBuilder(IServiceCollection services) 23 | { 24 | _services = Ensure.NotNull(services, nameof(services)); 25 | } 26 | 27 | /// 28 | /// Gets the set of services. 29 | /// 30 | public IServiceCollection Services => _services; 31 | 32 | /// 33 | /// Adds the auto-model feature to the services collection. 34 | /// 35 | /// The service builder. 36 | public ExtensionsServicesBuilder AddAutoModel() 37 | { 38 | _services.AddSingleton(); 39 | _services.AddScoped(); 40 | 41 | return this; 42 | } 43 | 44 | /// 45 | /// Adds the model builders from the given assembly. 46 | /// 47 | /// The assembly instance. 48 | /// The service builder. 49 | public ExtensionsServicesBuilder AddModelBuildersFromAssembly(Assembly assembly) 50 | { 51 | Ensure.NotNull(assembly, nameof(assembly)); 52 | 53 | var modelBuilderInterfaceType = typeof(IModelBuilder); 54 | 55 | var modelBuilderTypes = assembly.GetExportedTypes() 56 | .Select(t => t.GetTypeInfo()) 57 | .Where( 58 | ti => !ti.IsAbstract 59 | && ti.IsClass 60 | && ti.IsPublic 61 | && ti.ImplementedInterfaces.Any(i => modelBuilderInterfaceType.Equals(i))); 62 | 63 | foreach (var modelBuilderType in modelBuilderTypes) 64 | { 65 | _services.AddSingleton(modelBuilderInterfaceType, modelBuilderType.AsType()); 66 | } 67 | 68 | return this; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/DependencyInjection/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | /// 9 | /// Provides extensions for the type. 10 | /// 11 | public static class ServiceCollectionExtensions 12 | { 13 | /// 14 | /// Adds the entity framework core extensions to the services collection. 15 | /// 16 | /// The services collection. 17 | /// The builder action. 18 | /// The services collection. 19 | public static IServiceCollection AddEntityFrameworkCoreExtensions(this IServiceCollection services, Action builderAction) 20 | { 21 | Ensure.NotNull(services, nameof(services)); 22 | Ensure.NotNull(builderAction, nameof(builderAction)); 23 | 24 | var builder = new ExtensionsServicesBuilder(services); 25 | builderAction(builder); 26 | 27 | return services; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/EntityFrameworkCoreExtensions.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 04f3ac4f-e3fd-41c2-84e3-dc934dc276ea 11 | EntityFrameworkCoreExtensions 12 | .\obj 13 | .\bin\ 14 | v4.5 15 | 16 | 17 | 18 | 2.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/ExtendedDbContext.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using Microsoft.EntityFrameworkCore; 12 | using Microsoft.EntityFrameworkCore.ChangeTracking; 13 | 14 | /// 15 | /// Represents an extended database context with support for hooks. 16 | /// 17 | public class ExtendedDbContext : DbContext 18 | { 19 | private readonly Type _dbContextType; 20 | private ActionExecutor _executor; 21 | 22 | /// 23 | /// Initialises a new instance of 24 | /// 25 | /// The set of database context hooks. 26 | /// The database context options. 27 | public ExtendedDbContext(IEnumerable hooks, DbContextOptions options) 28 | : base(options) 29 | { 30 | _dbContextType = GetType(); 31 | _executor = CreateExecutor(Ensure.NotNull(hooks, nameof(hooks))); 32 | 33 | // MA - Flow the database context type to the hooks. 34 | _executor.Execute(h => h.SetDbContextType(_dbContextType)); 35 | } 36 | 37 | /// 38 | public override EntityEntry Add(TEntity entity) 39 | { 40 | _executor.Execute(hook => hook.AddingEntry(entity)); 41 | 42 | var entry = base.Add(entity); 43 | 44 | _executor.Execute(hook => hook.AddedEntry(entity, entry)); 45 | 46 | return entry; 47 | } 48 | 49 | /// 50 | public override EntityEntry Attach(TEntity entity) 51 | { 52 | _executor.Execute(hook => hook.AttachingEntry(entity)); 53 | 54 | var entry = base.Attach(entity); 55 | 56 | _executor.Execute(hook => hook.AttachedEntry(entity, entry)); 57 | 58 | return entry; 59 | } 60 | 61 | /// 62 | protected sealed override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 63 | { 64 | base.OnConfiguring(optionsBuilder); 65 | 66 | _executor.Execute(hook => hook.OnConfiguring(optionsBuilder)); 67 | } 68 | 69 | /// 70 | protected override void OnModelCreating(ModelBuilder modelBuilder) 71 | { 72 | base.OnModelCreating(modelBuilder); 73 | 74 | _executor.Execute(hook => hook.OnModelCreating(modelBuilder)); 75 | } 76 | 77 | /// 78 | public override EntityEntry Remove(TEntity entity) 79 | { 80 | _executor.Execute(hook => hook.RemovingEntry(entity)); 81 | 82 | var entry = base.Remove(entity); 83 | 84 | _executor.Execute(hook => hook.RemovedEntry(entity, entry)); 85 | 86 | return entry; 87 | } 88 | 89 | /// 90 | public override int SaveChanges(bool acceptAllChangesOnSuccess) 91 | { 92 | _executor.Execute(hook => hook.SavingChanges(acceptAllChangesOnSuccess)); 93 | 94 | int result = base.SaveChanges(acceptAllChangesOnSuccess); 95 | 96 | _executor.Execute(hook => hook.SavedChanges(result, acceptAllChangesOnSuccess)); 97 | 98 | return result; 99 | } 100 | 101 | /// 102 | public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken)) 103 | { 104 | _executor.Execute(async hook => await hook.SavingChangesAsync(acceptAllChangesOnSuccess, cancellationToken)); 105 | 106 | int results = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); 107 | 108 | _executor.Execute(async hook => await hook.SavedChangesAsync(results, acceptAllChangesOnSuccess, cancellationToken)); 109 | 110 | return results; 111 | } 112 | 113 | /// 114 | public override EntityEntry Update(TEntity entity) 115 | { 116 | _executor.Execute(hook => hook.UpdatingEntry(entity)); 117 | 118 | var entry = base.Update(entity); 119 | 120 | _executor.Execute(hook => hook.UpdatedEntry(entity, entry)); 121 | 122 | return entry; 123 | } 124 | 125 | private ActionExecutor CreateExecutor(IEnumerable hooks) 126 | { 127 | // MA - Get the untyped hooks. 128 | var untyped = hooks.Where(h => !h.IsTyped); 129 | 130 | // MA - Get the typed hooks. 131 | var typed = hooks.Where(h => h.IsTyped && h.DbContextType != null && h.DbContextType.IsAssignableFrom(_dbContextType)); 132 | 133 | // MA - Create the action executor. 134 | return new ActionExecutor(untyped.Concat(typed).ToArray()); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/FirstResultFuncExecutor.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System; 6 | using System.Linq; 7 | 8 | /// 9 | /// Converts an array enumeration into a delegated action flow that returns the first non-null result. 10 | /// 11 | /// The element type. 12 | public class FirstResultFuncExecutor 13 | { 14 | private readonly Func, object> _flow; 15 | 16 | /// 17 | /// Initialises a new instance of 18 | /// 19 | /// The source array. 20 | public FirstResultFuncExecutor(T[] items) 21 | { 22 | _flow = CreateFlow(Ensure.NotNull(items, nameof(items))); 23 | } 24 | 25 | /// 26 | /// Executes the given action over all items in the source array. 27 | /// 28 | /// The action to execute. 29 | public object Execute(Func action) 30 | => _flow?.Invoke(Ensure.NotNull(action, nameof(action))); 31 | 32 | /// 33 | /// Executes the given action over all items, halting at the first non-null result. 34 | /// 35 | /// The result type. 36 | /// The action to execute. 37 | /// The result instance, or null. 38 | public R Execute(Func action) where R : class 39 | => _flow?.Invoke(t => Ensure.NotNull(action, nameof(action))(t)) as R; 40 | 41 | private static Func, object> CreateFlow(T[] items) 42 | => items 43 | .Reverse() 44 | .Aggregate((Func, object>)null, (prev, item) => CreateFlowCore(item, prev)); 45 | 46 | private static Func, object> CreateFlowCore(T item, Func, object> prev) 47 | => (act) => 48 | { 49 | var result = act(item); 50 | if (result != null) 51 | { 52 | return result; 53 | } 54 | return prev?.Invoke(act); 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/IDbContextHook.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.EntityFrameworkCore; 9 | using Microsoft.EntityFrameworkCore.ChangeTracking; 10 | 11 | /// 12 | /// Defines the required contract for implementing a database context hook. 13 | /// 14 | public interface IDbContextHook 15 | { 16 | /// 17 | /// Gets the database context type. 18 | /// 19 | Type DbContextType { get; } 20 | 21 | /// 22 | /// Gets whether this is a type database context hook. 23 | /// 24 | bool IsTyped { get; } 25 | 26 | /// 27 | /// Fired when the database context is adding an entity. 28 | /// 29 | /// The entity type. 30 | /// The entity instance. 31 | void AddingEntry(TEntity entity) where TEntity : class; 32 | 33 | /// 34 | /// Fired when the database context has added an entry. 35 | /// 36 | /// The entity type. 37 | /// The entity instance. 38 | /// The entity entry. 39 | void AddedEntry(TEntity entity, EntityEntry entry) where TEntity : class; 40 | 41 | /// 42 | /// Fired when the database context is attaching an entity. 43 | /// 44 | /// The entity type. 45 | /// The entity instance. 46 | void AttachingEntry(TEntity entity) where TEntity : class; 47 | 48 | /// 49 | /// Fired when the database context has attached an entry. 50 | /// 51 | /// The entity type. 52 | /// The entity instance. 53 | /// The entity entry. 54 | void AttachedEntry(TEntity entity, EntityEntry entry) where TEntity : class; 55 | 56 | /// 57 | /// Fired when the database context is configuring. 58 | /// 59 | /// The options builder. 60 | void OnConfiguring(DbContextOptionsBuilder optionsBuilder); 61 | 62 | /// 63 | /// Fired when the model is being created for the database context. 64 | /// 65 | /// 66 | void OnModelCreating(ModelBuilder modelBuilder); 67 | 68 | /// 69 | /// Fired when the database context is removing an entity. 70 | /// 71 | /// The entity type. 72 | /// The entity instance. 73 | void RemovingEntry(TEntity entity) where TEntity : class; 74 | 75 | /// 76 | /// Fired when the database context has removed an entry. 77 | /// 78 | /// The entity type. 79 | /// The entity instance. 80 | /// The entity entry. 81 | void RemovedEntry(TEntity entity, EntityEntry entry) where TEntity : class; 82 | 83 | /// 84 | /// Fired when the database context is saving changes. 85 | /// 86 | /// Indicates whether the change tracker should accept changes after the changes have been sent successfully to the database. 87 | void SavingChanges(bool acceptAllChangesOnSuccess); 88 | 89 | /// 90 | /// Fired when the database context has saved changes. 91 | /// 92 | /// The number of state entries persisted to the database. 93 | /// Indicates whether the change tracker accepted changes after the changes had been sent successfully to the database. 94 | void SavedChanges(int persistedStateEntries, bool acceptedAllChangesOnSuccess); 95 | 96 | /// 97 | /// Fired when the database context is saving changes asynchronously. 98 | /// 99 | /// Indicates whether the change tracker should accept changes after the changes have been sent successfully to the database. 100 | /// The cancellation token to observe while waiting for the task to complete. 101 | /// A task instance used to await this method. 102 | Task SavingChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken); 103 | 104 | /// 105 | /// Fired when the database context has saved changes asynchronously. 106 | /// 107 | /// The number of state entries persisted to the database. 108 | /// Indicates whether the change tracker accepted changes after the changes had been sent successfully to the database. 109 | /// The cancellation token to observe while waiting for the task to complete. 110 | /// A task instance used to await this method. 111 | Task SavedChangesAsync(int persistedStateEntries, bool acceptedAllChangesOnSuccess, CancellationToken cancellationToken); 112 | 113 | /// 114 | /// Sets the database context type for this hook. 115 | /// 116 | /// The database context type. 117 | void SetDbContextType(Type type); 118 | 119 | /// 120 | /// Fired when the database context is updating an entity. 121 | /// 122 | /// The entity type. 123 | /// The entity instance. 124 | void UpdatingEntry(TEntity entity) where TEntity : class; 125 | 126 | /// 127 | /// Fired when the database context has updated an entry. 128 | /// 129 | /// The entity type. 130 | /// The entity instance. 131 | /// The entity entry. 132 | void UpdatedEntry(TEntity entity, EntityEntry entry) where TEntity : class; 133 | } 134 | 135 | /// 136 | /// Defines the required contract for implementing a database context hook. 137 | /// 138 | /// The database context type. 139 | public interface IDbContextHook : IDbContextHook 140 | where T : DbContext 141 | { 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Materialization/EntityMaterializerSourceHookBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Materialization 4 | { 5 | using System; 6 | using System.Linq.Expressions; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | 9 | /// 10 | /// Provides a base implementation of a . 11 | /// 12 | public class EntityMaterializerSourceHookBase : IEntityMaterializerSourceHook 13 | { 14 | /// 15 | public virtual Expression CreateMaterializeExpression(IEntityType entityType, Expression valueBuffer, int[] indexMap, Func defaultFactory) 16 | => null; 17 | 18 | /// 19 | public virtual Expression CreateReadValueCallExpression(Expression valueBuffer, int index, Func defaultFactory) 20 | => null; 21 | 22 | /// 23 | public virtual Expression CreateReadValueExpression(Expression valueBuffer, Type type, int index, Func defaultFactory) 24 | => null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Materialization/ExtendedEntityMaterializerSource.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Materialization 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using Microsoft.EntityFrameworkCore.Metadata; 10 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 11 | 12 | /// 13 | /// An extended entity materializer source with support for hooks. 14 | /// 15 | public class ExtendedEntityMaterializerSource : EntityMaterializerSource 16 | { 17 | private readonly FirstResultFuncExecutor _executor; 18 | 19 | /// 20 | /// Initialises a new instance of . 21 | /// 22 | /// The member mapper. 23 | /// The set of entity materializer source hooks. 24 | public ExtendedEntityMaterializerSource(IMemberMapper memberMapper, IEnumerable hooks) 25 | : base(memberMapper) 26 | { 27 | _executor = new FirstResultFuncExecutor(Ensure.NotNull(hooks, nameof(hooks)).ToArray()); 28 | } 29 | 30 | /// 31 | public override Expression CreateMaterializeExpression(IEntityType entityType, Expression valueBufferExpression, int[] indexMap = null) 32 | => _executor.Execute(hook => hook.CreateMaterializeExpression( 33 | entityType, 34 | valueBufferExpression, 35 | indexMap, 36 | (e, v, im) => base.CreateMaterializeExpression(e, v, im))) 37 | ?? base.CreateMaterializeExpression(entityType, valueBufferExpression, indexMap); 38 | 39 | /// 40 | public override Expression CreateReadValueCallExpression(Expression valueBuffer, int index) 41 | => _executor.Execute(hook => hook.CreateReadValueCallExpression( 42 | valueBuffer, 43 | index, 44 | (v, i) => base.CreateReadValueCallExpression(v, i))) 45 | ?? base.CreateReadValueCallExpression(valueBuffer, index); 46 | 47 | /// 48 | public override Expression CreateReadValueExpression(Expression valueBuffer, Type type, int index) 49 | => _executor.Execute(hook => hook.CreateReadValueExpression( 50 | valueBuffer, 51 | type, 52 | index, 53 | (v, t, i) => base.CreateReadValueExpression(v, t, i))) 54 | ?? base.CreateReadValueExpression(valueBuffer, type, index); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Materialization/IEntityMaterializerSourceHook.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Materialization 4 | { 5 | using System; 6 | using System.Linq.Expressions; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | using Microsoft.EntityFrameworkCore.Storage; 9 | 10 | /// 11 | /// Defines the required contract for implementing an entity materializer source hook. 12 | /// 13 | public interface IEntityMaterializerSourceHook 14 | { 15 | /// 16 | /// Creates the materialize expression 17 | /// 18 | /// The entity type 19 | /// The value buffer expression. 20 | /// The index map 21 | /// The factory used to create a default read value expression. 22 | /// The materialize expression. 23 | Expression CreateMaterializeExpression(IEntityType entityType, Expression valueBuffer, int[] indexMap, Func defaultFactory); 24 | 25 | /// 26 | /// Creates a read value expression that allows a value to be read from the value buffer. 27 | /// 28 | /// The value buffer expression. 29 | /// The property type. 30 | /// The value index within the buffer. 31 | /// The factory used to create a default read value expression. 32 | /// The read value expression. 33 | Expression CreateReadValueExpression(Expression valueBuffer, Type type, int index, Func defaultFactory); 34 | 35 | /// 36 | /// Creates a read value call expression for the indexer getter. 37 | /// 38 | /// The value buffer expression. 39 | /// The value index within the buffer. 40 | /// The factory used to create a default read value call expression. 41 | /// The read value call expression. 42 | Expression CreateReadValueCallExpression(Expression valueBuffer, int index, Func defaultFactory); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/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: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("EntityFrameworkCoreExtensions")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("04f3ac4f-e3fd-41c2-84e3-dc934dc276ea")] 20 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Storage/IRelationalTypeMapperHook.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Storage 4 | { 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | 8 | /// 9 | /// Defines the required contract for implementing a type mapper hook. 10 | /// 11 | public interface IRelationalTypeMapperHook 12 | { 13 | /// 14 | /// Gets the relational database type for the given property. 15 | /// 16 | /// The property to get the mapping for. 17 | /// The type mapping to be used. 18 | RelationalTypeMapping FindMapping(IProperty property); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/Storage/RelationalTypeMapperHookBase.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Storage 4 | { 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | 8 | /// 9 | /// Provides a base implementation of a relational type mapper hook. 10 | /// 11 | public abstract class RelationalTypeMapperHookBase : IRelationalTypeMapperHook 12 | { 13 | /// 14 | public virtual RelationalTypeMapping FindMapping(IProperty property) 15 | => null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/EntityFrameworkCoreExtensions/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "description": "Provides hooks and extensions for the Entity Framework Core project", 4 | "packOptions": { 5 | "tags": [ 6 | "Entity Framework Core Extensions", 7 | "Entity Framework Core", 8 | "entity-framework-core-extensions", 9 | "entity-framework-core", 10 | "ef-extensions", 11 | "ef", 12 | "Data", 13 | "O/RM" 14 | ] 15 | }, 16 | "buildOptions": { 17 | "warningsAsErrors": true, 18 | "xmlDoc": true, 19 | "compile": { 20 | "include": "../Shared/*.cs" 21 | } 22 | }, 23 | "dependencies": { 24 | "Microsoft.EntityFrameworkCore": "1.0.1", 25 | "Microsoft.EntityFrameworkCore.Relational": "1.0.1" 26 | }, 27 | "frameworks": { 28 | "net451": {}, 29 | "netstandard1.3": {} 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Shared/Ensure.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System; 6 | using System.Diagnostics; 7 | 8 | /// 9 | /// Provides argument checking services. 10 | /// 11 | [DebuggerStepThrough] 12 | internal static class Ensure 13 | { 14 | /// 15 | /// Ensures the given value is not null. 16 | /// 17 | /// The parameter type 18 | /// The parameter value 19 | /// The parameter name 20 | /// If the given argument is null. 21 | /// The parameter value 22 | public static T NotNull(T value, string parameterName) where T : class 23 | { 24 | if (value == null) 25 | { 26 | throw new ArgumentNullException(parameterName); 27 | } 28 | 29 | return value; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Shared/TaskCache.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions 4 | { 5 | using System.Threading.Tasks; 6 | 7 | /// 8 | /// Provides a cached completed task. 9 | /// 10 | internal static class TaskCache 11 | { 12 | /// 13 | /// A completed task. 14 | /// 15 | public static readonly Task CompletedTask = Task.FromResult(true); 16 | } 17 | } -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.SqlServer.Tests/EntityFrameworkCoreExtensions.SqlServer.Tests.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 95a0954b-d79e-4944-9a39-8d7f03efbb43 10 | EntityFrameworkCoreExtensions.Tests 11 | .\obj 12 | .\bin\ 13 | v4.5 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.SqlServer.Tests/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: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("EntityFrameworkCoreExtensions.SqlServer.Tests")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("95a0954b-d79e-4944-9a39-8d7f03efbb43")] 20 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.SqlServer.Tests/Storage/ExtendedSqlServerTypeMapperTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Tests.Storage 4 | { 5 | using System; 6 | using System.Data; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 9 | using Microsoft.EntityFrameworkCore.Storage; 10 | using Xunit; 11 | 12 | using EntityFrameworkCoreExtensions.Storage; 13 | 14 | /// 15 | /// Provides tests for the type. 16 | /// 17 | public class ExtendedSqlServerTypeMapperTests 18 | { 19 | [Fact] 20 | public void Constructor_ValidatesParameters() 21 | { 22 | // Arrange 23 | 24 | // Act 25 | 26 | // Assert 27 | Assert.Throws(() => new ExtendedSqlServerTypeMapper(null /* hooks */)); 28 | } 29 | 30 | [Fact] 31 | public void FindMapping_WithProperty_ReturnsCustomMappingFromHook() 32 | { 33 | // Arrange 34 | var model = new Model(); 35 | var property = model.AddEntityType("Test").AddProperty("Value", typeof(CustomType)); 36 | var hook = new TestRelationalTypeMapperHook(); 37 | var hooks = new IRelationalTypeMapperHook[] { hook }; 38 | var typeMapper = new ExtendedSqlServerTypeMapper(hooks); 39 | 40 | // Act 41 | var typeMapping = typeMapper.FindMapping(property); 42 | 43 | // Assert 44 | Assert.NotNull(typeMapping); 45 | Assert.Equal(typeMapping.ClrType, typeof(CustomType)); 46 | } 47 | 48 | [Fact] 49 | public void FindMapping_WithProperty_ReturnsStandardMappingFromBase() 50 | { 51 | // Arrange 52 | var model = new Model(); 53 | var property = model.AddEntityType("Test").AddProperty("Value", typeof(byte)); 54 | var hook = new TestRelationalTypeMapperHook(); 55 | var hooks = new IRelationalTypeMapperHook[] { hook }; 56 | var typeMapper = new ExtendedSqlServerTypeMapper(hooks); 57 | 58 | // Act 59 | var typeMapping = typeMapper.FindMapping(property); 60 | 61 | // Assert 62 | Assert.NotNull(typeMapping); 63 | Assert.Equal(typeMapping.ClrType, typeof(byte)); 64 | } 65 | 66 | private struct CustomType 67 | { 68 | 69 | } 70 | 71 | private class TestRelationalTypeMapperHook : RelationalTypeMapperHookBase 72 | { 73 | private readonly RelationalTypeMapping _customType 74 | = new RelationalTypeMapping("varchar(100)", typeof(CustomType), dbType: DbType.String, unicode: false, size: 100); 75 | 76 | public override RelationalTypeMapping FindMapping(IProperty property) 77 | { 78 | return (_customType.ClrType.Equals(property.ClrType)) 79 | ? _customType 80 | : null; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.SqlServer.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildOptions": { 3 | "warningsAsErrors": true 4 | }, 5 | "dependencies": { 6 | "dotnet-test-xunit": "2.2.0-*", 7 | "xunit": "2.2.0-*", 8 | "Moq": "4.6.36-*", 9 | "Microsoft.EntityFrameworkCore.SqlServer": "1.0.1", 10 | "EntityFrameworkCoreExtensions.SqlServer": { "target": "project" } 11 | }, 12 | "testRunner": "xunit", 13 | "frameworks": { 14 | "netcoreapp1.1": { 15 | "dependencies": { 16 | "Microsoft.NETCore.App": { 17 | "version": "1.0.1", 18 | "type": "platform" 19 | } 20 | } 21 | }, 22 | "net451": {} 23 | } 24 | } -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/ActionExecutorTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Tests 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using Xunit; 8 | 9 | /// 10 | /// Provides tests for the type. 11 | /// 12 | public class ActionExecutorTests 13 | { 14 | [Fact] 15 | public void Constructor_ValidatesParameters() 16 | { 17 | // Arrange 18 | 19 | // Act 20 | 21 | // Assert 22 | Assert.Throws(() => new ActionExecutor(null /* items */)); 23 | } 24 | 25 | [Fact] 26 | public void Execute_ValidatesParameters() 27 | { 28 | // Arrange 29 | var items = new[] { "Hello", "World" }; 30 | var executor = new ActionExecutor(items); 31 | 32 | // Act 33 | 34 | // Assert 35 | Assert.Throws(() => executor.Execute(null /* action */)); 36 | } 37 | 38 | [Fact] 39 | public void Execute_FlowsCorrectOrder() 40 | { 41 | // Arrange 42 | var items = new[] { "Hello", "World" }; 43 | var executor = new ActionExecutor(items); 44 | var results = new List(); 45 | 46 | // Act 47 | executor.Execute(s => results.Add(s)); 48 | 49 | // Assert 50 | Assert.Equal(items, results); 51 | } 52 | 53 | [Fact] 54 | public void Accepts_EmptyArray() 55 | { 56 | // Arrange 57 | var items = new string[0]; 58 | var executor = new ActionExecutor(items); 59 | bool called = false; 60 | 61 | // Act 62 | executor.Execute(s => called = true); 63 | 64 | // Assert 65 | Assert.Equal(false, called); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/Builder/AutoModelDbContextHookTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Tests.Builder 4 | { 5 | using System; 6 | using System.Linq; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.EntityFrameworkCore.Metadata.Conventions; 9 | using Xunit; 10 | 11 | using EntityFrameworkCoreExtensions.Builder; 12 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 13 | 14 | /// 15 | /// Provides tests for the type. 16 | /// 17 | public class AutoModelDbContextHookTests 18 | { 19 | [Fact] 20 | public void Constructor_ValidatesParameters() 21 | { 22 | // Arrange 23 | 24 | // Act 25 | 26 | // Assert 27 | Assert.Throws(() => new AutoModelDbContextHook(null /* modelBuilderService */)); 28 | } 29 | 30 | [Fact] 31 | public void OnModelCreating_DoesNothing_IfNoDbContextType() 32 | { 33 | // Arrange 34 | var modelBuilderService = new ModelBuilderService(new IModelBuilder[0]); 35 | var hook = new AutoModelDbContextHook(modelBuilderService); 36 | var modelBuilder = new ModelBuilder(new ConventionSet()); 37 | 38 | // Act 39 | // MA - the DbContextType property is set by the ExtendedDbContext constructor, so its currently null. 40 | hook.OnModelCreating(modelBuilder); 41 | 42 | // Assert 43 | var entityTypes = modelBuilder.Model.GetEntityTypes().ToArray(); 44 | Assert.Equal(0, entityTypes.Length); 45 | } 46 | 47 | [Fact] 48 | public void OnModelCreating_DoesNothing_IfNoAutoBuildAttribute() 49 | { 50 | // Arrange 51 | var modelBuilderService = new ModelBuilderService(new IModelBuilder[0]); 52 | var hook = new AutoModelDbContextHook(modelBuilderService); 53 | var modelBuilder = new ModelBuilder(new ConventionSet()); 54 | 55 | // Act 56 | ((IDbContextHook)hook).SetDbContextType(typeof(NoAutoBuildCatalogDbContext)); 57 | hook.OnModelCreating(modelBuilder); 58 | 59 | // Assert 60 | var entityTypes = modelBuilder.Model.GetEntityTypes().ToArray(); 61 | Assert.Equal(0, entityTypes.Length); 62 | } 63 | 64 | [Fact] 65 | public void OnModelCreating_BuildsModel() 66 | { 67 | // Arrange 68 | var productEntityTypeBuilder = new ProductEntityTypeBuilder(); 69 | var productVariantEntityTypeBuilder = new ProductVariantEntityTypeBuilder(); 70 | var inputBuilders = new IModelBuilder[] 71 | { 72 | productEntityTypeBuilder, 73 | productVariantEntityTypeBuilder 74 | }; 75 | var modelBuilderService = new ModelBuilderService(inputBuilders); 76 | var hook = new AutoModelDbContextHook(modelBuilderService); 77 | var modelBuilder = new ModelBuilder(new ConventionSet()); 78 | 79 | // Act 80 | ((IDbContextHook)hook).SetDbContextType(typeof(AutoBuildCatalogDbContext)); 81 | hook.OnModelCreating(modelBuilder); 82 | 83 | // Assert 84 | var entityTypes = modelBuilder.Model.GetEntityTypes().ToArray(); 85 | Assert.Equal(2, entityTypes.Length); 86 | Assert.Equal(true, productEntityTypeBuilder.BuildEntityCalled); 87 | Assert.Contains(entityTypes, et => et.ClrType == typeof(Product)); 88 | Assert.Equal(true, productVariantEntityTypeBuilder.BuildEntityCalled); 89 | Assert.Contains(entityTypes, et => et.ClrType == typeof(ProductVariant)); 90 | } 91 | 92 | [Fact] 93 | public void OnModelCreating_BuildsModel_CanIgnoreNavigations() 94 | { 95 | // Arrange 96 | var productEntityTypeBuilder = new ProductEntityTypeBuilder(); 97 | var productVariantEntityTypeBuilder = new ProductVariantEntityTypeBuilder(); 98 | var inputBuilders = new IModelBuilder[] 99 | { 100 | productEntityTypeBuilder, 101 | productVariantEntityTypeBuilder 102 | }; 103 | var modelBuilderService = new ModelBuilderService(inputBuilders); 104 | var hook = new AutoModelDbContextHook(modelBuilderService); 105 | var modelBuilder = new ModelBuilder(new ConventionSet()); 106 | 107 | // Act 108 | ((IDbContextHook)hook).SetDbContextType(typeof(NoNavigationsAutoBuildCatalogDbContext)); 109 | hook.OnModelCreating(modelBuilder); 110 | 111 | // Assert 112 | var entityTypes = modelBuilder.Model.GetEntityTypes().ToArray(); 113 | Assert.Equal(1, entityTypes.Length); 114 | Assert.Equal(true, productEntityTypeBuilder.BuildEntityCalled); 115 | Assert.Contains(entityTypes, et => et.ClrType == typeof(Product)); 116 | } 117 | 118 | [Fact] 119 | public void OnModelCreating_BuildsModel_UsingTypedContextModelBuilder() 120 | { 121 | // Arrange 122 | var contextModelBuilder = new ContextModelBuilder(); 123 | var inputBuilders = new IModelBuilder[] 124 | { 125 | contextModelBuilder 126 | }; 127 | var modelBuilderService = new ModelBuilderService(inputBuilders); 128 | var hook = new AutoModelDbContextHook(modelBuilderService); 129 | var modelBuilder = new ModelBuilder(new ConventionSet()); 130 | 131 | // Act 132 | ((IDbContextHook)hook).SetDbContextType(typeof(AutoBuildCatalogDbContext)); 133 | hook.OnModelCreating(modelBuilder); 134 | 135 | // Assert 136 | var entityTypes = modelBuilder.Model.GetEntityTypes().ToArray(); 137 | Assert.Equal(1, entityTypes.Length); 138 | Assert.Equal(true, contextModelBuilder.BuildModelCalled); 139 | Assert.Contains(entityTypes, et => et.ClrType == typeof(Vendor)); 140 | } 141 | 142 | private class Vendor 143 | { 144 | 145 | } 146 | 147 | private class ContextModelBuilder : ModelBuilderBase 148 | { 149 | public bool BuildModelCalled { get; private set; } 150 | public override void BuildModel(ModelBuilder builder) 151 | { 152 | BuildModelCalled = true; 153 | 154 | builder.Entity(); 155 | } 156 | } 157 | 158 | private class Product 159 | { 160 | public ProductVariant ProductVariant { get; set; } 161 | } 162 | 163 | private class ProductEntityTypeBuilder : EntityTypeBuilderBase 164 | { 165 | public bool BuildEntityCalled { get; set; } 166 | public override void BuildEntity(EntityTypeBuilder builder) 167 | => BuildEntityCalled = true; 168 | } 169 | 170 | private class ProductVariant 171 | { 172 | 173 | } 174 | 175 | private class ProductVariantEntityTypeBuilder : EntityTypeBuilderBase 176 | { 177 | public bool BuildEntityCalled { get; set; } 178 | public override void BuildEntity(EntityTypeBuilder builder) 179 | => BuildEntityCalled = true; 180 | } 181 | 182 | private class NoAutoBuildCatalogDbContext : DbContext 183 | { 184 | public DbSet Products { get; set; } 185 | } 186 | 187 | [AutoModel] 188 | private class AutoBuildCatalogDbContext : DbContext 189 | { 190 | public DbSet Products { get; set; } 191 | } 192 | 193 | [AutoModel(IncludeNavigations = false)] 194 | private class NoNavigationsAutoBuildCatalogDbContext : DbContext 195 | { 196 | public DbSet Products { get; set; } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/Builder/ModelBuilderServiceTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Tests.Builder 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 9 | using Xunit; 10 | 11 | using EntityFrameworkCoreExtensions.Builder; 12 | 13 | /// 14 | /// Provides tests for the type. 15 | /// 16 | public class ModelBuilderServiceTests 17 | { 18 | [Fact] 19 | public void Constructor_ValidatesParameters() 20 | { 21 | // Arrange 22 | 23 | // Act 24 | 25 | // Assert 26 | Assert.Throws(() => new ModelBuilderService(null /* builders */)); 27 | } 28 | 29 | [Fact] 30 | public void GetModelBuilders_ValidatesParameters() 31 | { 32 | // Arrange 33 | var dbContextType = typeof(CatalogDbContext); 34 | var inputBuilders = new IModelBuilder[0]; 35 | var service = new ModelBuilderService(inputBuilders); 36 | 37 | // Act 38 | 39 | // Assert 40 | Assert.Throws(() => service.GetModelBuilders(null /* dbContextType */, false)); 41 | } 42 | 43 | [Fact] 44 | public void GetModelBuilders_SupportsUntypedModelBuilders() 45 | { 46 | // Arrange 47 | var dbContextType = typeof(CatalogDbContext); 48 | var inputBuilders = new IModelBuilder[] 49 | { 50 | new UntypedModelBuilder() 51 | }; 52 | var service = new ModelBuilderService(inputBuilders); 53 | 54 | // Act 55 | var outputBuilders = service.GetModelBuilders(dbContextType, false); 56 | 57 | // Assert 58 | Assert.Equal(1, outputBuilders.Length); 59 | Assert.Contains(inputBuilders[0], outputBuilders); 60 | } 61 | 62 | [Fact] 63 | public void GetModelBuilders_SupportsContextTypedModelBuilders() 64 | { 65 | // Arrange 66 | var dbContextType = typeof(CatalogDbContext); 67 | var inputBuilders = new IModelBuilder[] 68 | { 69 | new CatalogDbContextModelBuilder(), 70 | new OtherDbContextModelBuilder() 71 | }; 72 | var service = new ModelBuilderService(inputBuilders); 73 | 74 | // Act 75 | var outputBuilders = service.GetModelBuilders(dbContextType, false); 76 | 77 | // Assert 78 | Assert.Equal(1, outputBuilders.Length); 79 | Assert.Contains(inputBuilders[0], outputBuilders); 80 | } 81 | 82 | [Fact] 83 | public void GetModelBuilders_SupportsEntityTypedModelBuilders_FromDbSetProperties() 84 | { 85 | // Arrange 86 | var dbContextType = typeof(CatalogDbContext); 87 | var inputBuilders = new IModelBuilder[] 88 | { 89 | new ProductEntityTypeBuilder(), 90 | new ProductVariantEntityTypeBuilder(), 91 | new VendorEntityTypeBuilder(), 92 | new AttributeEntityTypeBuilder() 93 | }; 94 | var service = new ModelBuilderService(inputBuilders); 95 | 96 | // Act 97 | var outputBuilders = service.GetModelBuilders(dbContextType, false /* includeNavigations */); 98 | 99 | // Assert 100 | Assert.Equal(3, outputBuilders.Length); 101 | Assert.Contains(inputBuilders[0], outputBuilders); 102 | Assert.Contains(inputBuilders[1], outputBuilders); 103 | Assert.Contains(inputBuilders[2], outputBuilders); 104 | } 105 | 106 | [Fact] 107 | public void GetModelBuilders_SupportsEntityTypeModelBuilders_FromSingularNavigation() 108 | { 109 | // Arrange 110 | var dbContextType = typeof(CatalogDbContext); 111 | var inputBuilders = new IModelBuilder[] 112 | { 113 | new ProductOptionEntityTypeBuilder() 114 | }; 115 | var service = new ModelBuilderService(inputBuilders); 116 | 117 | // Act 118 | var outputBuilders = service.GetModelBuilders(dbContextType, true /* includeNavigations */); 119 | 120 | // Assert 121 | Assert.Equal(1, outputBuilders.Length); 122 | Assert.Contains(inputBuilders[0], outputBuilders); 123 | } 124 | 125 | [Fact] 126 | public void GetModelBuilders_SupportsEntityTypeModelBuilders_FromICollectionNavigation() 127 | { 128 | // Arrange 129 | var dbContextType = typeof(CatalogDbContext); 130 | var inputBuilders = new IModelBuilder[] 131 | { 132 | new AttributeEntityTypeBuilder() 133 | }; 134 | var service = new ModelBuilderService(inputBuilders); 135 | 136 | // Act 137 | var outputBuilders = service.GetModelBuilders(dbContextType, true /* includeNavigations */); 138 | 139 | // Assert 140 | Assert.Equal(1, outputBuilders.Length); 141 | Assert.Contains(inputBuilders[0], outputBuilders); 142 | } 143 | 144 | [Fact] 145 | public void GetModelBuilders_SupportsEntityTypeModelBuilders_FromListNavigation() 146 | { 147 | // Arrange 148 | var dbContextType = typeof(CatalogDbContext); 149 | var inputBuilders = new IModelBuilder[] 150 | { 151 | new VendorOptionEntityTypeBuilder() 152 | }; 153 | var service = new ModelBuilderService(inputBuilders); 154 | 155 | // Act 156 | var outputBuilders = service.GetModelBuilders(dbContextType, true /* includeNavigations */); 157 | 158 | // Assert 159 | Assert.Equal(1, outputBuilders.Length); 160 | Assert.Contains(inputBuilders[0], outputBuilders); 161 | } 162 | 163 | [Fact] 164 | public void GetModelBuilders_SupportsEntityTypeModelBuilders_FromIListNavigation() 165 | { 166 | // Arrange 167 | var dbContextType = typeof(CatalogDbContext); 168 | var inputBuilders = new IModelBuilder[] 169 | { 170 | new ProductVariantOptionEntityTypeBuilder() 171 | }; 172 | var service = new ModelBuilderService(inputBuilders); 173 | 174 | // Act 175 | var outputBuilders = service.GetModelBuilders(dbContextType, true /* includeNavigations */); 176 | 177 | // Assert 178 | Assert.Equal(1, outputBuilders.Length); 179 | Assert.Contains(inputBuilders[0], outputBuilders); 180 | } 181 | 182 | private class Product 183 | { 184 | public Vendor Vendor { get; set; } 185 | 186 | public ICollection Products { get; set; } 187 | 188 | public ICollection Attributes { get; set; } 189 | 190 | public ProductOption ProductOption { get; set; } 191 | 192 | public string Name { get; set; } 193 | 194 | public decimal Cost { get; set; } 195 | } 196 | 197 | private class ProductEntityTypeBuilder : EntityTypeBuilderBase 198 | { 199 | public override void BuildEntity(EntityTypeBuilder builder) 200 | { 201 | 202 | } 203 | } 204 | 205 | private class ProductVariant 206 | { 207 | public Product Product { get; set; } 208 | 209 | public IList Options { get; set; } 210 | } 211 | 212 | private class ProductVariantEntityTypeBuilder : EntityTypeBuilderBase 213 | { 214 | public override void BuildEntity(EntityTypeBuilder builder) 215 | { 216 | 217 | } 218 | } 219 | 220 | private class Vendor 221 | { 222 | public List Products { get; set; } 223 | 224 | public List Options { get; set; } 225 | } 226 | 227 | private class VendorOption 228 | { 229 | 230 | } 231 | 232 | private class VendorOptionEntityTypeBuilder : EntityTypeBuilderBase 233 | { 234 | public override void BuildEntity(EntityTypeBuilder builder) 235 | { 236 | } 237 | } 238 | 239 | private class VendorEntityTypeBuilder : EntityTypeBuilderBase 240 | { 241 | public override void BuildEntity(EntityTypeBuilder builder) 242 | { 243 | 244 | } 245 | } 246 | 247 | private class Attribute 248 | { 249 | 250 | } 251 | 252 | private class AttributeEntityTypeBuilder : EntityTypeBuilderBase 253 | { 254 | public override void BuildEntity(EntityTypeBuilder builder) 255 | { 256 | 257 | } 258 | } 259 | 260 | private class ProductOption 261 | { 262 | 263 | } 264 | 265 | private class ProductOptionEntityTypeBuilder : EntityTypeBuilderBase 266 | { 267 | public override void BuildEntity(EntityTypeBuilder builder) 268 | { 269 | } 270 | } 271 | 272 | private class ProductVariantOption 273 | { 274 | 275 | } 276 | 277 | private class ProductVariantOptionEntityTypeBuilder : EntityTypeBuilderBase 278 | { 279 | public override void BuildEntity(EntityTypeBuilder builder) 280 | { 281 | } 282 | } 283 | 284 | private class CatalogDbContextModelBuilder : ModelBuilderBase 285 | { 286 | public override void BuildModel(ModelBuilder builder) 287 | { 288 | 289 | } 290 | } 291 | 292 | private class UntypedModelBuilder : ModelBuilderBase 293 | { 294 | public override void BuildModel(ModelBuilder builder) 295 | { 296 | 297 | } 298 | } 299 | 300 | private class CatalogDbContext : DbContext 301 | { 302 | public DbSet Vendors { get; set; } 303 | 304 | public DbSet Products { get; set; } 305 | 306 | public DbSet ProductVariants { get; set; } 307 | } 308 | 309 | private class OtherDbContext : DbContext { } 310 | 311 | private class OtherDbContextModelBuilder : ModelBuilderBase 312 | { 313 | public override void BuildModel(ModelBuilder builder) 314 | { 315 | 316 | } 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/ChangeTracking/ExtendedChangeDetectorTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Tests.ChangeTracking 4 | { 5 | using System; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Xunit; 10 | 11 | using EntityFrameworkCoreExtensions.ChangeTracking; 12 | 13 | /// 14 | /// Provides tests for the type. 15 | /// 16 | public class ExtendedChangeDetectorTests 17 | { 18 | [Fact] 19 | public void Constructor_ValidatesParameters() 20 | { 21 | // Arrange 22 | 23 | // Act 24 | 25 | // Assert 26 | Assert.Throws(() => new ExtendedChangeDetector(null /* hooks */)); 27 | } 28 | 29 | [Fact] 30 | public void DetectionHooks_AreCalled() 31 | { 32 | // Arrange 33 | var hook = new TestChangeDetectorHook(); 34 | var hooks = new IChangeDetectorHook[] { hook }; 35 | var changeDetector = new ExtendedChangeDetector(hooks); 36 | var services = new ServiceCollection() 37 | .AddEntityFrameworkInMemoryDatabase() 38 | .AddSingleton(sp => changeDetector); 39 | var serviceProvider = services.BuildServiceProvider(); 40 | 41 | using (var context = new CatalogDBContext(serviceProvider)) 42 | { 43 | // MA - We have to add a entity, otherwise no change tracking entries will be tested. 44 | context.Add(new Product()); 45 | 46 | // Act 47 | context.ChangeTracker.DetectChanges(); 48 | 49 | // Assert 50 | Assert.Equal(true, hook.DetectingChangesCalled); 51 | Assert.Equal(true, hook.DetectingEntryChangesCalled); 52 | Assert.Equal(true, hook.DetectedEntryChangesCalled); 53 | Assert.Equal(true, hook.DetectedChangesCalled); 54 | } 55 | } 56 | 57 | private class TestChangeDetectorHook : ChangeDetectorHookBase 58 | { 59 | public bool DetectedChangesCalled { get; private set; } 60 | 61 | public override void DetectedChanges(IChangeDetector changeDetector, IStateManager stateManager) 62 | => DetectedChangesCalled = true; 63 | 64 | public bool DetectingChangesCalled { get; private set; } 65 | 66 | public override void DetectingChanges(IChangeDetector changeDetector, IStateManager stateManager) 67 | => DetectingChangesCalled = true; 68 | 69 | public bool DetectedEntryChangesCalled { get; private set; } 70 | 71 | public override void DetectedEntryChanges(IChangeDetector changeDetector, IStateManager stateManager, InternalEntityEntry entry) 72 | => DetectedEntryChangesCalled = true; 73 | 74 | public bool DetectingEntryChangesCalled { get; private set; } 75 | 76 | public override void DetectingEntryChanges(IChangeDetector changeDetector, IStateManager stateManager, InternalEntityEntry entry) 77 | => DetectingEntryChangesCalled = true; 78 | } 79 | 80 | private class Product 81 | { 82 | public int Id { get; set; } 83 | } 84 | 85 | private class CatalogDBContext : DbContext 86 | { 87 | private IServiceProvider _serviceProvider; 88 | 89 | public CatalogDBContext(IServiceProvider serviceProvider = null) 90 | { 91 | _serviceProvider = serviceProvider; 92 | } 93 | 94 | public DbSet Products { get; set; } 95 | 96 | protected override void OnModelCreating(ModelBuilder modelBuilder) 97 | { 98 | modelBuilder.Entity().HasKey(p => p.Id); 99 | } 100 | 101 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 102 | { 103 | optionsBuilder.UseInMemoryDatabase(); 104 | 105 | if (_serviceProvider != null) 106 | { 107 | optionsBuilder.UseInternalServiceProvider(_serviceProvider); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/EntityFrameworkCoreExtensions.Tests.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 92cb8734-adb3-4e79-9020-1b09bd235275 10 | EntityFrameworkCoreExtensions.Tests 11 | .\obj 12 | .\bin\ 13 | v4.5 14 | 15 | 16 | 2.0 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/ExtendedDbContextTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Tests 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.EntityFrameworkCore.ChangeTracking; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Xunit; 13 | 14 | /// 15 | /// Provides tests for the type. 16 | /// 17 | public class ExtendedDbContextTests 18 | { 19 | [Fact] 20 | public void Constructor_ValidatesParameters() 21 | { 22 | // Arrange 23 | var hooks = new IDbContextHook[0]; 24 | 25 | // Act 26 | 27 | // Assert 28 | Assert.Throws(() => new ExtendedDbContext(null /* hooks */, null /* options */)); 29 | Assert.Throws(() => new ExtendedDbContext(hooks, null /* options */)); 30 | } 31 | 32 | [Fact] 33 | public void Supports_UntypedHooks() 34 | { 35 | // Arrange 36 | var untypedHook = new UntypedDbContextHook(); 37 | var hooks = new IDbContextHook[] 38 | { 39 | untypedHook 40 | }; 41 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 42 | var serviceProvider = services.BuildServiceProvider(); 43 | var options = new DbContextOptionsBuilder() 44 | .UseInMemoryDatabase() 45 | .UseInternalServiceProvider(serviceProvider) 46 | .Options; 47 | var context = new TestExtendedDbContext(hooks, options); 48 | 49 | // Act 50 | // MA - Services are lazily initiated so we need to perform an operation to set them. 51 | context.SaveChanges(); 52 | 53 | 54 | // Assert 55 | Assert.Equal(true, untypedHook.OnConfiguringCalled); 56 | } 57 | 58 | [Fact] 59 | public void DbContextType_AppliedToHooks() 60 | { 61 | // Arrange 62 | var untypedHook = new UntypedDbContextHook(); 63 | var hooks = new IDbContextHook[] 64 | { 65 | untypedHook 66 | }; 67 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 68 | var serviceProvider = services.BuildServiceProvider(); 69 | var options = new DbContextOptionsBuilder() 70 | .UseInMemoryDatabase() 71 | .UseInternalServiceProvider(serviceProvider) 72 | .Options; 73 | 74 | // Act 75 | var context = new TestExtendedDbContext(hooks, options); 76 | 77 | // Assert 78 | Assert.Equal(typeof(TestExtendedDbContext), untypedHook.DbContextType); 79 | } 80 | 81 | [Fact] 82 | public void Supports_TypedHooks() 83 | { 84 | // Arrange 85 | var typedHook = new TypedDbContextHook(); 86 | var otherTypedHook = new OtherTypedDbContextHook(); 87 | var hooks = new IDbContextHook[] 88 | { 89 | typedHook, otherTypedHook 90 | }; 91 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 92 | var serviceProvider = services.BuildServiceProvider(); 93 | var options = new DbContextOptionsBuilder() 94 | .UseInMemoryDatabase() 95 | .UseInternalServiceProvider(serviceProvider) 96 | .Options; 97 | var context = new TestExtendedDbContext(hooks, options); 98 | 99 | // Act 100 | // MA - Services are lazily initiated so we need to perform an operation to set them. 101 | context.SaveChanges(); 102 | 103 | 104 | // Assert 105 | Assert.Equal(true, typedHook.OnConfiguringCalled); 106 | // MA - THis should be false, because otherTypedHook is a typed hook for a different DbContext type. 107 | Assert.Equal(false, otherTypedHook.OnConfiguringCalled); 108 | } 109 | 110 | [Fact] 111 | public void AddEntity_CallsHooks() 112 | { 113 | // Arrange 114 | var hook = new UntypedDbContextHook(); 115 | var hooks = new IDbContextHook[] 116 | { 117 | hook 118 | }; 119 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 120 | var serviceProvider = services.BuildServiceProvider(); 121 | var options = new DbContextOptionsBuilder() 122 | .UseInMemoryDatabase() 123 | .UseInternalServiceProvider(serviceProvider) 124 | .Options; 125 | var context = new TestExtendedDbContext(hooks, options); 126 | 127 | // Act 128 | var product = new Product { }; 129 | context.Add(product); 130 | 131 | // Assert 132 | Assert.Equal(true, hook.AddingEntryCalled); 133 | Assert.Equal(true, hook.AddedEntryCalled); 134 | } 135 | 136 | [Fact] 137 | public void AttachEntity_CallsHooks() 138 | { 139 | // Arrange 140 | var hook = new UntypedDbContextHook(); 141 | var hooks = new IDbContextHook[] 142 | { 143 | hook 144 | }; 145 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 146 | var serviceProvider = services.BuildServiceProvider(); 147 | var options = new DbContextOptionsBuilder() 148 | .UseInMemoryDatabase() 149 | .UseInternalServiceProvider(serviceProvider) 150 | .Options; 151 | var context = new TestExtendedDbContext(hooks, options); 152 | 153 | // Act 154 | var product = new Product { Id = 1 }; 155 | context.Attach(product); 156 | 157 | // Assert 158 | Assert.Equal(true, hook.AttachingEntryCalled); 159 | Assert.Equal(true, hook.AttachedEntryCalled); 160 | } 161 | 162 | [Fact] 163 | public void OnConfiguring_CallsHooks() 164 | { 165 | // Arrange 166 | var hook = new UntypedDbContextHook(); 167 | var hooks = new IDbContextHook[] 168 | { 169 | hook 170 | }; 171 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 172 | var serviceProvider = services.BuildServiceProvider(); 173 | var options = new DbContextOptionsBuilder() 174 | .UseInMemoryDatabase() 175 | .UseInternalServiceProvider(serviceProvider) 176 | .Options; 177 | var context = new TestExtendedDbContext(hooks, options); 178 | 179 | // Act 180 | var product = new Product { }; 181 | // MA - Services are lazily initiated so we need to perform an operation to set them. 182 | context.Add(product); 183 | 184 | // Assert 185 | Assert.Equal(true, hook.OnConfiguringCalled); 186 | } 187 | 188 | [Fact] 189 | public void OnModelCreating_CallsHooks() 190 | { 191 | // Arrange 192 | var hook = new UntypedDbContextHook(); 193 | var hooks = new IDbContextHook[] 194 | { 195 | hook 196 | }; 197 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 198 | var serviceProvider = services.BuildServiceProvider(); 199 | var options = new DbContextOptionsBuilder() 200 | .UseInMemoryDatabase() 201 | .UseInternalServiceProvider(serviceProvider) 202 | .Options; 203 | var context = new TestExtendedDbContext(hooks, options); 204 | 205 | // Act 206 | var product = new Product { }; 207 | // MA - Services are lazily initiated so we need to perform an operation to set them. 208 | context.Add(product); 209 | 210 | // Assert 211 | Assert.Equal(true, hook.OnModelCreatingCalled); 212 | } 213 | 214 | [Fact] 215 | public void RemoveEntity_CallsHooks() 216 | { 217 | // Arrange 218 | var hook = new UntypedDbContextHook(); 219 | var hooks = new IDbContextHook[] 220 | { 221 | hook 222 | }; 223 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 224 | var serviceProvider = services.BuildServiceProvider(); 225 | var options = new DbContextOptionsBuilder() 226 | .UseInMemoryDatabase() 227 | .UseInternalServiceProvider(serviceProvider) 228 | .Options; 229 | var context = new TestExtendedDbContext(hooks, options); 230 | 231 | // Act 232 | var product = new Product { Id = 1 }; 233 | context.Remove(product); 234 | 235 | // Assert 236 | Assert.Equal(true, hook.RemovingEntryCalled); 237 | Assert.Equal(true, hook.RemovedEntryCalled); 238 | } 239 | 240 | [Fact] 241 | public void SavingChanges_CallsHooks() 242 | { 243 | // Arrange 244 | var hook = new UntypedDbContextHook(); 245 | var hooks = new IDbContextHook[] 246 | { 247 | hook 248 | }; 249 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 250 | var serviceProvider = services.BuildServiceProvider(); 251 | var options = new DbContextOptionsBuilder() 252 | .UseInMemoryDatabase() 253 | .UseInternalServiceProvider(serviceProvider) 254 | .Options; 255 | var context = new TestExtendedDbContext(hooks, options); 256 | 257 | // Act 258 | var product = new Product { }; 259 | context.Add(product); 260 | context.SaveChanges(); 261 | 262 | // Assert 263 | Assert.Equal(true, hook.SavingChangesCalled); 264 | Assert.Equal(true, hook.SavedChangesCalled); 265 | } 266 | 267 | [Fact] 268 | public async Task SavingChangesAsync_CallsHooks() 269 | { 270 | // Arrange 271 | var hook = new UntypedDbContextHook(); 272 | var hooks = new IDbContextHook[] 273 | { 274 | hook 275 | }; 276 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 277 | var serviceProvider = services.BuildServiceProvider(); 278 | var options = new DbContextOptionsBuilder() 279 | .UseInMemoryDatabase() 280 | .UseInternalServiceProvider(serviceProvider) 281 | .Options; 282 | var context = new TestExtendedDbContext(hooks, options); 283 | 284 | // Act 285 | var product = new Product { }; 286 | context.Add(product); 287 | await context.SaveChangesAsync(); 288 | 289 | // Assert 290 | Assert.Equal(true, hook.SavingChangesAsyncCalled); 291 | Assert.Equal(true, hook.SavedChangesAsyncCalled); 292 | } 293 | 294 | [Fact] 295 | public void UpdateEntity_CallsHooks() 296 | { 297 | // Arrange 298 | var hook = new UntypedDbContextHook(); 299 | var hooks = new IDbContextHook[] 300 | { 301 | hook 302 | }; 303 | var services = new ServiceCollection().AddEntityFrameworkInMemoryDatabase(); 304 | var serviceProvider = services.BuildServiceProvider(); 305 | var options = new DbContextOptionsBuilder() 306 | .UseInMemoryDatabase() 307 | .UseInternalServiceProvider(serviceProvider) 308 | .Options; 309 | var context = new TestExtendedDbContext(hooks, options); 310 | 311 | // Act 312 | var product = new Product { Id = 1 }; 313 | context.Update(product); 314 | 315 | // Assert 316 | Assert.Equal(true, hook.UpdatingEntryCalled); 317 | Assert.Equal(true, hook.UpdatedEntryCalled); 318 | } 319 | 320 | public class Product 321 | { 322 | public int Id { get; set; } 323 | } 324 | 325 | private class UntypedDbContextHook : DbContextHookBase 326 | { 327 | public bool AddingEntryCalled { get; private set; } 328 | public override void AddingEntry(TEntity entity) 329 | => AddingEntryCalled = true; 330 | 331 | public bool AddedEntryCalled { get; private set; } 332 | public override void AddedEntry(TEntity entity, EntityEntry entry) 333 | => AddedEntryCalled = true; 334 | 335 | public bool AttachingEntryCalled { get; private set; } 336 | public override void AttachingEntry(TEntity entity) 337 | => AttachingEntryCalled = true; 338 | 339 | public bool AttachedEntryCalled { get; private set; } 340 | public override void AttachedEntry(TEntity entity, EntityEntry entry) 341 | => AttachedEntryCalled = true; 342 | 343 | public bool OnConfiguringCalled { get; private set; } 344 | public override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 345 | => OnConfiguringCalled = true; 346 | 347 | public bool OnModelCreatingCalled { get; private set; } 348 | public override void OnModelCreating(ModelBuilder modelBuilder) 349 | => OnModelCreatingCalled = true; 350 | 351 | public bool RemovingEntryCalled { get; private set; } 352 | public override void RemovingEntry(TEntity entity) 353 | => RemovingEntryCalled = true; 354 | 355 | public bool RemovedEntryCalled { get; private set; } 356 | public override void RemovedEntry(TEntity entity, EntityEntry entry) 357 | => RemovedEntryCalled = true; 358 | 359 | public bool SavingChangesCalled { get; private set; } 360 | public override void SavingChanges(bool acceptAllChangesOnSuccess) 361 | => SavingChangesCalled = true; 362 | 363 | public bool SavedChangesCalled { get; private set; } 364 | public override void SavedChanges(int persistedStateEntries, bool acceptedAllChangesOnSuccess) 365 | => SavedChangesCalled = true; 366 | 367 | public bool SavingChangesAsyncCalled { get; private set; } 368 | public override Task SavingChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken) 369 | { 370 | SavingChangesAsyncCalled = true; 371 | 372 | return Task.FromResult(true); 373 | } 374 | 375 | public bool SavedChangesAsyncCalled { get; private set; } 376 | public override Task SavedChangesAsync(int persistedStateEntries, bool acceptedAllChangesOnSuccess, CancellationToken cancellationToken) 377 | { 378 | SavedChangesAsyncCalled = true; 379 | 380 | return Task.FromResult(true); 381 | } 382 | 383 | public bool UpdatingEntryCalled { get; private set; } 384 | public override void UpdatingEntry(TEntity entity) 385 | => UpdatingEntryCalled = true; 386 | 387 | public bool UpdatedEntryCalled { get; private set; } 388 | public override void UpdatedEntry(TEntity entity, EntityEntry entry) 389 | => UpdatedEntryCalled = true; 390 | } 391 | 392 | private class TypedDbContextHook : DbContextHookBase 393 | { 394 | public bool AddingEntryCalled { get; private set; } 395 | public override void AddingEntry(TEntity entity) 396 | => AddingEntryCalled = true; 397 | 398 | public bool AddedEntryCalled { get; private set; } 399 | public override void AddedEntry(TEntity entity, EntityEntry entry) 400 | => AddedEntryCalled = true; 401 | 402 | public bool AttachingEntryCalled { get; private set; } 403 | public override void AttachingEntry(TEntity entity) 404 | => AttachingEntryCalled = true; 405 | 406 | public bool AttachedEntryCalled { get; private set; } 407 | public override void AttachedEntry(TEntity entity, EntityEntry entry) 408 | => AttachedEntryCalled = true; 409 | 410 | public bool OnConfiguringCalled { get; private set; } 411 | public override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 412 | => OnConfiguringCalled = true; 413 | 414 | public bool OnModelCreatingCalled { get; private set; } 415 | public override void OnModelCreating(ModelBuilder modelBuilder) 416 | => OnModelCreatingCalled = true; 417 | 418 | public bool RemovingEntryCalled { get; private set; } 419 | public override void RemovingEntry(TEntity entity) 420 | => RemovingEntryCalled = true; 421 | 422 | public bool RemovedEntryCalled { get; private set; } 423 | public override void RemovedEntry(TEntity entity, EntityEntry entry) 424 | => RemovedEntryCalled = true; 425 | 426 | public bool SavingChangesCalled { get; private set; } 427 | public override void SavingChanges(bool acceptAllChangesOnSuccess) 428 | => SavingChangesCalled = true; 429 | 430 | public bool SavedChangesCalled { get; private set; } 431 | public override void SavedChanges(int persistedStateEntries, bool acceptedAllChangesOnSuccess) 432 | => SavedChangesCalled = true; 433 | 434 | public bool SavingChangesAsyncCalled { get; private set; } 435 | public override Task SavingChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken) 436 | { 437 | SavingChangesAsyncCalled = true; 438 | 439 | return Task.FromResult(true); 440 | } 441 | 442 | public bool SavedChangesAsyncCalled { get; private set; } 443 | public override Task SavedChangesAsync(int persistedStateEntries, bool acceptedAllChangesOnSuccess, CancellationToken cancellationToken) 444 | { 445 | SavedChangesAsyncCalled = true; 446 | 447 | return Task.FromResult(true); 448 | } 449 | 450 | public bool UpdatingEntryCalled { get; private set; } 451 | public override void UpdatingEntry(TEntity entity) 452 | => UpdatingEntryCalled = true; 453 | 454 | public bool UpdatedEntryCalled { get; private set; } 455 | public override void UpdatedEntry(TEntity entity, EntityEntry entry) 456 | => UpdatedEntryCalled = true; 457 | } 458 | 459 | private class OtherTypedDbContextHook : DbContextHookBase 460 | { 461 | public bool AddingEntryCalled { get; private set; } 462 | public override void AddingEntry(TEntity entity) 463 | => AddingEntryCalled = true; 464 | 465 | public bool AddedEntryCalled { get; private set; } 466 | public override void AddedEntry(TEntity entity, EntityEntry entry) 467 | => AddedEntryCalled = true; 468 | 469 | public bool AttachingEntryCalled { get; private set; } 470 | public override void AttachingEntry(TEntity entity) 471 | => AttachingEntryCalled = true; 472 | 473 | public bool AttachedEntryCalled { get; private set; } 474 | public override void AttachedEntry(TEntity entity, EntityEntry entry) 475 | => AttachedEntryCalled = true; 476 | 477 | public bool OnConfiguringCalled { get; private set; } 478 | public override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 479 | => OnConfiguringCalled = true; 480 | 481 | public bool OnModelCreatingCalled { get; private set; } 482 | public override void OnModelCreating(ModelBuilder modelBuilder) 483 | => OnModelCreatingCalled = true; 484 | 485 | public bool RemovingEntryCalled { get; private set; } 486 | public override void RemovingEntry(TEntity entity) 487 | => RemovingEntryCalled = true; 488 | 489 | public bool RemovedEntryCalled { get; private set; } 490 | public override void RemovedEntry(TEntity entity, EntityEntry entry) 491 | => RemovedEntryCalled = true; 492 | 493 | public bool SavingChangesCalled { get; private set; } 494 | public override void SavingChanges(bool acceptAllChangesOnSuccess) 495 | => SavingChangesCalled = true; 496 | 497 | public bool SavedChangesCalled { get; private set; } 498 | public override void SavedChanges(int persistedStateEntries, bool acceptedAllChangesOnSuccess) 499 | => SavedChangesCalled = true; 500 | 501 | public bool SavingChangesAsyncCalled { get; private set; } 502 | public override Task SavingChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken) 503 | { 504 | SavingChangesAsyncCalled = true; 505 | 506 | return Task.FromResult(true); 507 | } 508 | 509 | public bool SavedChangesAsyncCalled { get; private set; } 510 | public override Task SavedChangesAsync(int persistedStateEntries, bool acceptedAllChangesOnSuccess, CancellationToken cancellationToken) 511 | { 512 | SavedChangesAsyncCalled = true; 513 | 514 | return Task.FromResult(true); 515 | } 516 | 517 | public bool UpdatingEntryCalled { get; private set; } 518 | public override void UpdatingEntry(TEntity entity) 519 | => UpdatingEntryCalled = true; 520 | 521 | public bool UpdatedEntryCalled { get; private set; } 522 | public override void UpdatedEntry(TEntity entity, EntityEntry entry) 523 | => UpdatedEntryCalled = true; 524 | } 525 | 526 | private class TestExtendedDbContext : ExtendedDbContext 527 | { 528 | public TestExtendedDbContext(IEnumerable hooks, DbContextOptions options) : base(hooks, options) 529 | { 530 | } 531 | 532 | protected override void OnModelCreating(ModelBuilder modelBuilder) 533 | { 534 | base.OnModelCreating(modelBuilder); 535 | 536 | modelBuilder.Entity().HasKey(p => p.Id); 537 | } 538 | } 539 | 540 | private class OtherTestExtendedDbContext : ExtendedDbContext 541 | { 542 | public OtherTestExtendedDbContext(IEnumerable hooks, DbContextOptions options) : base(hooks, options) 543 | { 544 | } 545 | } 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/FirstResultFuncExecutorTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Tests 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using Xunit; 8 | 9 | /// 10 | /// Provides tests for the type. 11 | /// 12 | public class FirstResultFuncExecutorTests 13 | { 14 | [Fact] 15 | public void Constructor_ValidatesParameters() 16 | { 17 | // Arrange 18 | 19 | // Act 20 | 21 | // Assert 22 | Assert.Throws(() => new FirstResultFuncExecutor(null /* items */)); 23 | } 24 | 25 | [Fact] 26 | public void Execute_ValidatesParameters() 27 | { 28 | // Arrange 29 | var products = new[] 30 | { 31 | new Product(), new Product() 32 | }; 33 | var executor = new FirstResultFuncExecutor(products); 34 | 35 | // Act 36 | 37 | // Assert 38 | Assert.Throws(() => executor.Execute(null /* action */)); 39 | } 40 | 41 | [Fact] 42 | public void Execute_ReturnsFirstItem() 43 | { 44 | // Arrange 45 | var products = new[] 46 | { 47 | new Product() { Name = "Product A" }, 48 | new Product() { Name = "Product B" } 49 | }; 50 | var executor = new FirstResultFuncExecutor(products); 51 | 52 | // Act 53 | string name = executor.Execute(p => p.Name); 54 | 55 | // Assert 56 | Assert.Equal("Product A", name); 57 | } 58 | 59 | [Fact] 60 | public void Accepts_EmptyArray() 61 | { 62 | // Arrange 63 | var products = new Product[0]; 64 | var executor = new FirstResultFuncExecutor(products); 65 | 66 | // Act 67 | string name = executor.Execute(p => p.Name); 68 | 69 | // Assert 70 | Assert.Equal(null, name); 71 | } 72 | 73 | private class Product 74 | { 75 | public string Name { get; set; } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/Materialization/ExtendedEntityMaterializerSourceTests.cs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 2 | 3 | namespace EntityFrameworkCoreExtensions.Tests.Materialization 4 | { 5 | using System; 6 | using System.Linq.Expressions; 7 | using Microsoft.EntityFrameworkCore; 8 | using Microsoft.EntityFrameworkCore.Metadata; 9 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 10 | using Microsoft.EntityFrameworkCore.Storage; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Moq; 13 | using Xunit; 14 | 15 | using EntityFrameworkCoreExtensions.Materialization; 16 | 17 | public class ExtendedEntityMaterializerSourceTests 18 | { 19 | [Fact] 20 | public void Constructor_ValidatesParameters() 21 | { 22 | // Arrange 23 | var memberMapper = new MemberMapper(new FieldMatcher()); 24 | 25 | // Act 26 | 27 | // Assert 28 | Assert.Throws(() => new ExtendedEntityMaterializerSource(null /* memberMapper */, null /* hooks */)); 29 | Assert.Throws(() => new ExtendedEntityMaterializerSource(memberMapper, null /* hooks */)); 30 | } 31 | 32 | [Fact] 33 | public void CreateReadValueCallExpression_CallsHook() 34 | { 35 | // Arrange 36 | var memberMapper = new MemberMapper(new FieldMatcher()); 37 | var testExpression = Expression.Constant(1); 38 | var hook = new TestEntityMaterializeSourceHook() 39 | { 40 | TestExpression = testExpression 41 | }; 42 | var hooks = new IEntityMaterializerSourceHook[] { hook }; 43 | var source = new ExtendedEntityMaterializerSource(memberMapper, hooks); 44 | 45 | // Act 46 | var expression = source.CreateReadValueCallExpression(_readerParameter, 0); 47 | 48 | // Assert 49 | Assert.Equal(true, hook.CreateReadValueCallExpressionCalled); 50 | Assert.Equal(testExpression, expression); 51 | } 52 | 53 | [Fact] 54 | public void CreateReadValueExpression_CallsHook() 55 | { 56 | // Arrange 57 | var memberMapper = new MemberMapper(new FieldMatcher()); 58 | var testExpression = Expression.Constant(1); 59 | var hook = new TestEntityMaterializeSourceHook() 60 | { 61 | TestExpression = testExpression 62 | }; 63 | var hooks = new IEntityMaterializerSourceHook[] { hook }; 64 | var source = new ExtendedEntityMaterializerSource(memberMapper, hooks); 65 | 66 | // Act 67 | var expression = source.CreateReadValueExpression(_readerParameter, typeof(string), 0); 68 | 69 | // Assert 70 | Assert.Equal(true, hook.CreateReadValueExpressionCalled); 71 | Assert.Equal(testExpression, expression); 72 | } 73 | 74 | [Fact] 75 | public void CreateMaterializeExpression_CallsHook() 76 | { 77 | // Arrange 78 | var memberMapper = new MemberMapper(new FieldMatcher()); 79 | var entityType = new Model().AddEntityType(typeof(Product)); 80 | var testExpression = Expression.Constant(1); 81 | var hook = new TestEntityMaterializeSourceHook() 82 | { 83 | TestExpression = testExpression 84 | }; 85 | var hooks = new IEntityMaterializerSourceHook[] { hook }; 86 | var source = new ExtendedEntityMaterializerSource(memberMapper, hooks); 87 | 88 | // Act 89 | var expression = source.CreateMaterializeExpression(entityType, _readerParameter); 90 | 91 | // Assert 92 | Assert.Equal(true, hook.CreateMaterializeExpressionCalled); 93 | Assert.Equal(testExpression, expression); 94 | } 95 | 96 | private static readonly ParameterExpression _readerParameter = Expression.Parameter(typeof(ValueBuffer), "valueBuffer"); 97 | 98 | private Func GetMaterializer(IEntityMaterializerSource source, IEntityType entityType) 99 | => Expression.Lambda>( 100 | source.CreateMaterializeExpression(entityType, _readerParameter), 101 | _readerParameter).Compile(); 102 | 103 | private class Product 104 | { 105 | } 106 | 107 | private class TestEntityMaterializeSourceHook : EntityMaterializerSourceHookBase 108 | { 109 | public Expression TestExpression { get; set; } 110 | 111 | public bool CreateMaterializeExpressionCalled { get; private set; } 112 | 113 | public override Expression CreateMaterializeExpression(IEntityType entityType, Expression valueBuffer, int[] indexMap, Func defaultFactory) 114 | { 115 | CreateMaterializeExpressionCalled = true; 116 | 117 | return TestExpression; 118 | } 119 | 120 | public bool CreateReadValueCallExpressionCalled { get; private set; } 121 | 122 | public override Expression CreateReadValueCallExpression(Expression valueBuffer, int index, Func defaultFactory) 123 | { 124 | CreateReadValueCallExpressionCalled = true; 125 | 126 | return TestExpression; 127 | } 128 | 129 | public bool CreateReadValueExpressionCalled { get; private set; } 130 | 131 | public override Expression CreateReadValueExpression(Expression valueBuffer, Type type, int index, Func defaultFactory) 132 | { 133 | CreateReadValueExpressionCalled = true; 134 | 135 | return TestExpression; 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/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: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("EntityFrameworkCoreExtensionTests")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("92cb8734-adb3-4e79-9020-1b09bd235275")] 20 | -------------------------------------------------------------------------------- /test/EntityFrameworkCoreExtensions.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildOptions": { 3 | "warningsAsErrors": true 4 | }, 5 | "dependencies": { 6 | "dotnet-test-xunit": "2.2.0-*", 7 | "xunit": "2.2.0-*", 8 | "Moq": "4.6.36-*", 9 | "Microsoft.EntityFrameworkCore.InMemory": "1.0.1", 10 | "EntityFrameworkCoreExtensions": { "target": "project" } 11 | }, 12 | "testRunner": "xunit", 13 | "frameworks": { 14 | "netcoreapp1.1": { 15 | "dependencies": { 16 | "Microsoft.NETCore.App": { 17 | "version": "1.0.1", 18 | "type": "platform" 19 | } 20 | } 21 | }, 22 | "net451": {} 23 | } 24 | } --------------------------------------------------------------------------------