├── .gitattributes ├── .gitignore ├── .travis.yml ├── EntityFramework.LazyLoading.sln ├── LICENSE ├── README.md ├── samples └── Microsoft.EntityFrameworkCore.LazyLoading.Sample │ ├── Data │ ├── DbInitializer.cs │ ├── Factory │ │ └── SchoolContextFactory.cs │ └── SchoolContext.cs │ ├── Microsoft.EntityFrameworkCore.LazyLoading.Sample.csproj │ ├── Migrations │ ├── 20170327180841_Initial.Designer.cs │ ├── 20170327180841_Initial.cs │ └── SchoolContextModelSnapshot.cs │ ├── Models │ ├── Course.cs │ ├── CourseAssignment.cs │ ├── Department.cs │ ├── Enrollment.cs │ ├── Instructor.cs │ ├── OfficeAssignment.cs │ ├── Person.cs │ └── Student.cs │ └── Program.cs ├── src └── Microsoft.EntityFrameworkCore.LazyLoading │ ├── Exceptions │ └── LazyLoadingConfigurationException.cs │ ├── Infrastructure │ └── Internal │ │ └── LazyLoadingOptionsExtension.cs │ ├── Internal │ ├── ConcurrencyDetector.cs │ └── IConcurrencyDetector.cs │ ├── LazyCollection.cs │ ├── LazyLoadingDbContextOptionsBuilderExtensions.cs │ ├── LazyReference.cs │ ├── Metadata │ └── Internal │ │ ├── ILazyLoadingEntityMaterializerSource.cs │ │ └── LazyLoadingEntityMaterializerSource.cs │ ├── Microsoft.EntityFrameworkCore.LazyLoading.csproj │ └── Query │ └── Internal │ └── PerDbContextCompiledQueryCache.cs └── tests └── Microsoft.EntityFrameworkCore.LazyLoading.Tests ├── Configuration ├── Config.cs └── DatabaseConfig.cs ├── Data ├── DbInitializer.cs ├── SchoolContext.cs └── SchoolContextFactory.cs ├── DatabaseTestBase.cs ├── Microsoft.EntityFrameworkCore.LazyLoading.Tests.csproj ├── Models ├── Course.cs ├── CourseAssignment.cs ├── Department.cs ├── Enrollment.cs ├── Instructor.cs ├── OfficeAssignment.cs ├── Person.cs └── Student.cs ├── Tests.cs └── appsettings.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: EntityFramework.LazyLoading.sln 3 | mono: none 4 | dotnet: 1.0.1 5 | dist: trusty 6 | sudo: required 7 | services: 8 | - mysql 9 | - postgresql 10 | env: 11 | global: 12 | - DB_NAME=ContosoUniversity 13 | matrix: 14 | - DB=PostgreSql CONFIGURATION=Release 15 | #- DB=MySql CONFIGURATION=Release 16 | install: 17 | - dotnet restore 18 | before_script: 19 | - export Microsoft_EntityFrameworkCore_LazyLoading_Tests_DatabaseType=$DB 20 | #- sh -c "if [ '$DB' = 'MySql' ]; then mysql -e 'CREATE DATABASE \`$DB_NAME\`; USE \`$DB_NAME\`; CREATE TABLE \`__EFMigrationsHistory\` (\`MigrationId\` nvarchar(150) NOT NULL, \`ProductVersion\` nvarchar(32) NOT NULL, PRIMARY KEY (\`MigrationId\`));'; (cd tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests && dotnet ef database update); fi" 21 | script: 22 | - dotnet build --configuration $CONFIGURATION 23 | - (cd tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests && dotnet test --configuration $CONFIGURATION) -------------------------------------------------------------------------------- /EntityFramework.LazyLoading.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{46D6F802-03D6-42D0-8BA7-D7D15863CB1C}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{4B5016C4-E1A4-44CE-9F37-6264691567B6}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.EntityFrameworkCore.LazyLoading.Sample", "samples\Microsoft.EntityFrameworkCore.LazyLoading.Sample\Microsoft.EntityFrameworkCore.LazyLoading.Sample.csproj", "{F7F9810A-6634-42DF-800D-F3E439FB21FB}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.EntityFrameworkCore.LazyLoading", "src\Microsoft.EntityFrameworkCore.LazyLoading\Microsoft.EntityFrameworkCore.LazyLoading.csproj", "{A8542DA0-CC74-4643-BED6-5EDE322470F7}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{12D5BAB5-F68A-4610-A55E-09A727525B6B}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.EntityFrameworkCore.LazyLoading.Tests", "tests\Microsoft.EntityFrameworkCore.LazyLoading.Tests\Microsoft.EntityFrameworkCore.LazyLoading.Tests.csproj", "{2FB7164B-0BBB-4EDE-A745-357F4CE2498B}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {F7F9810A-6634-42DF-800D-F3E439FB21FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {F7F9810A-6634-42DF-800D-F3E439FB21FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {F7F9810A-6634-42DF-800D-F3E439FB21FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {F7F9810A-6634-42DF-800D-F3E439FB21FB}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {A8542DA0-CC74-4643-BED6-5EDE322470F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {A8542DA0-CC74-4643-BED6-5EDE322470F7}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {A8542DA0-CC74-4643-BED6-5EDE322470F7}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {A8542DA0-CC74-4643-BED6-5EDE322470F7}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {2FB7164B-0BBB-4EDE-A745-357F4CE2498B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {2FB7164B-0BBB-4EDE-A745-357F4CE2498B}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {2FB7164B-0BBB-4EDE-A745-357F4CE2498B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {2FB7164B-0BBB-4EDE-A745-357F4CE2498B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(NestedProjects) = preSolution 41 | {F7F9810A-6634-42DF-800D-F3E439FB21FB} = {4B5016C4-E1A4-44CE-9F37-6264691567B6} 42 | {A8542DA0-CC74-4643-BED6-5EDE322470F7} = {46D6F802-03D6-42D0-8BA7-D7D15863CB1C} 43 | {2FB7164B-0BBB-4EDE-A745-357F4CE2498B} = {12D5BAB5-F68A-4610-A55E-09A727525B6B} 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alexis Nowikowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EntityFramework.LazyLoading [![Build Status](https://travis-ci.org/darxis/EntityFramework.LazyLoading.svg?branch=dev)](https://travis-ci.org/darxis/EntityFramework.LazyLoading) 2 | Lazy Loading for EF Core 3 | 4 | Inspired by and partially based on the blog post: https://weblogs.asp.net/ricardoperes/implementing-missing-features-in-entity-framework-core-part-6-lazy-loading 5 | 6 | # How to enable LazyLoading in EF Core? 7 | 8 | Enabling LazyLoading in EF Core is extremely easy with this library. You just need to call `UseLazyLoading()` (see step 2 below). 9 | 10 | However, you will need to slightly modify your entity classes, but just the References, not the Collections (see step 3 below). 11 | 12 | ## Step 1 - Nuget package 13 | Reference the `Microsoft.EntityFrameworkCore.LazyLoading` NuGet package (https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.LazyLoading/). 14 | ## Step 2 - Configure the DbContext builder 15 | Call `UseLazyLoading()` on the `DbContextOptionsBuilder` when creating the `DbContext`. 16 | ```c# 17 | public class MyDbContextFactory : IDbContextFactory 18 | { 19 | private readonly bool _isLazy; 20 | 21 | public MyDbContextFactory (bool isLazy) 22 | { 23 | _isLazy = isLazy; 24 | } 25 | 26 | public MyDbContext Create(DbContextFactoryOptions options) 27 | { 28 | var dbContextOptionsBuilder = new DbContextOptionsBuilder(); 29 | dbContextOptionsBuilder.UseSqlServer(""); 30 | 31 | // Here we enable LazyLoading 32 | dbContextOptionsBuilder.UseLazyLoading(); 33 | 34 | return new MyDbContext(dbContextOptionsBuilder.Options); 35 | } 36 | } 37 | ``` 38 | ## Step 3 - Adjust the model 39 | In your model you need to declare References using the type `LazyReference`. Collections don't require additional configuration in your model, just use the `ICollection<>` type. 40 | ```c# 41 | public class Parent 42 | { 43 | public ICollection Childs { get; set; } 44 | } 45 | 46 | public class Child 47 | { 48 | private LazyReference _parentLazy = new LazyReference(); 49 | public Parent Parent 50 | { 51 | get 52 | { 53 | return _parentLazy.GetValue(this); 54 | } 55 | set 56 | { 57 | _parentLazy.SetValue(value); 58 | } 59 | } 60 | } 61 | ``` 62 | ## Step 4 - Done! 63 | That's all, LazyLoading enabled! It was so easy, wasn't it? 64 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Data/DbInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data 6 | { 7 | public static class DbInitializer 8 | { 9 | public static void Initialize(SchoolContext context) 10 | { 11 | //context.Database.EnsureCreated(); 12 | 13 | // Look for any students. 14 | if (context.Students.Any()) 15 | { 16 | return; // DB has been seeded 17 | } 18 | 19 | var students = new Student[] 20 | { 21 | new Student { FirstMidName = "Carson", LastName = "Alexander", 22 | EnrollmentDate = DateTime.Parse("2010-09-01") }, 23 | new Student { FirstMidName = "Meredith", LastName = "Alonso", 24 | EnrollmentDate = DateTime.Parse("2012-09-01") }, 25 | new Student { FirstMidName = "Arturo", LastName = "Anand", 26 | EnrollmentDate = DateTime.Parse("2013-09-01") }, 27 | new Student { FirstMidName = "Gytis", LastName = "Barzdukas", 28 | EnrollmentDate = DateTime.Parse("2012-09-01") }, 29 | new Student { FirstMidName = "Yan", LastName = "Li", 30 | EnrollmentDate = DateTime.Parse("2012-09-01") }, 31 | new Student { FirstMidName = "Peggy", LastName = "Justice", 32 | EnrollmentDate = DateTime.Parse("2011-09-01") }, 33 | new Student { FirstMidName = "Laura", LastName = "Norman", 34 | EnrollmentDate = DateTime.Parse("2013-09-01") }, 35 | new Student { FirstMidName = "Nino", LastName = "Olivetto", 36 | EnrollmentDate = DateTime.Parse("2005-09-01") } 37 | }; 38 | 39 | foreach (Student s in students) 40 | { 41 | context.Students.Add(s); 42 | } 43 | context.SaveChanges(); 44 | 45 | var instructors = new Instructor[] 46 | { 47 | new Instructor { FirstMidName = "Kim", LastName = "Abercrombie", 48 | HireDate = DateTime.Parse("1995-03-11") }, 49 | new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri", 50 | HireDate = DateTime.Parse("2002-07-06") }, 51 | new Instructor { FirstMidName = "Roger", LastName = "Harui", 52 | HireDate = DateTime.Parse("1998-07-01") }, 53 | new Instructor { FirstMidName = "Candace", LastName = "Kapoor", 54 | HireDate = DateTime.Parse("2001-01-15") }, 55 | new Instructor { FirstMidName = "Roger", LastName = "Zheng", 56 | HireDate = DateTime.Parse("2004-02-12") } 57 | }; 58 | 59 | foreach (Instructor i in instructors) 60 | { 61 | context.Instructors.Add(i); 62 | } 63 | context.SaveChanges(); 64 | 65 | var departments = new Department[] 66 | { 67 | new Department { Name = "English", Budget = 350000, 68 | StartDate = DateTime.Parse("2007-09-01"), 69 | InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID }, 70 | new Department { Name = "Mathematics", Budget = 100000, 71 | StartDate = DateTime.Parse("2007-09-01"), 72 | InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID }, 73 | new Department { Name = "Engineering", Budget = 350000, 74 | StartDate = DateTime.Parse("2007-09-01"), 75 | InstructorID = instructors.Single( i => i.LastName == "Harui").ID }, 76 | new Department { Name = "Economics", Budget = 100000, 77 | StartDate = DateTime.Parse("2007-09-01"), 78 | InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID } 79 | }; 80 | 81 | foreach (Department d in departments) 82 | { 83 | context.Departments.Add(d); 84 | } 85 | context.SaveChanges(); 86 | 87 | var courses = new Course[] 88 | { 89 | new Course {CourseID = 1050, Title = "Chemistry", Credits = 3, 90 | DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID 91 | }, 92 | new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3, 93 | DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID 94 | }, 95 | new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3, 96 | DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID 97 | }, 98 | new Course {CourseID = 1045, Title = "Calculus", Credits = 4, 99 | DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID 100 | }, 101 | new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4, 102 | DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID 103 | }, 104 | new Course {CourseID = 2021, Title = "Composition", Credits = 3, 105 | DepartmentID = departments.Single( s => s.Name == "English").DepartmentID 106 | }, 107 | new Course {CourseID = 2042, Title = "Literature", Credits = 4, 108 | DepartmentID = departments.Single( s => s.Name == "English").DepartmentID 109 | }, 110 | }; 111 | 112 | foreach (Course c in courses) 113 | { 114 | context.Courses.Add(c); 115 | } 116 | context.SaveChanges(); 117 | 118 | var officeAssignments = new OfficeAssignment[] 119 | { 120 | new OfficeAssignment { 121 | InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID, 122 | Location = "Smith 17" }, 123 | new OfficeAssignment { 124 | InstructorID = instructors.Single( i => i.LastName == "Harui").ID, 125 | Location = "Gowan 27" }, 126 | new OfficeAssignment { 127 | InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID, 128 | Location = "Thompson 304" }, 129 | }; 130 | 131 | foreach (OfficeAssignment o in officeAssignments) 132 | { 133 | context.OfficeAssignments.Add(o); 134 | } 135 | context.SaveChanges(); 136 | 137 | var courseInstructors = new CourseAssignment[] 138 | { 139 | new CourseAssignment { 140 | CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 141 | InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID 142 | }, 143 | new CourseAssignment { 144 | CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 145 | InstructorID = instructors.Single(i => i.LastName == "Harui").ID 146 | }, 147 | new CourseAssignment { 148 | CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 149 | InstructorID = instructors.Single(i => i.LastName == "Zheng").ID 150 | }, 151 | new CourseAssignment { 152 | CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 153 | InstructorID = instructors.Single(i => i.LastName == "Zheng").ID 154 | }, 155 | new CourseAssignment { 156 | CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 157 | InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID 158 | }, 159 | new CourseAssignment { 160 | CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 161 | InstructorID = instructors.Single(i => i.LastName == "Harui").ID 162 | }, 163 | new CourseAssignment { 164 | CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 165 | InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID 166 | }, 167 | new CourseAssignment { 168 | CourseID = courses.Single(c => c.Title == "Literature" ).CourseID, 169 | InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID 170 | }, 171 | }; 172 | 173 | foreach (CourseAssignment ci in courseInstructors) 174 | { 175 | context.CourseAssignments.Add(ci); 176 | } 177 | context.SaveChanges(); 178 | 179 | var enrollments = new Enrollment[] 180 | { 181 | new Enrollment { 182 | StudentID = students.Single(s => s.LastName == "Alexander").ID, 183 | CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 184 | Grade = Grade.A 185 | }, 186 | new Enrollment { 187 | StudentID = students.Single(s => s.LastName == "Alexander").ID, 188 | CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 189 | Grade = Grade.C 190 | }, 191 | new Enrollment { 192 | StudentID = students.Single(s => s.LastName == "Alexander").ID, 193 | CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 194 | Grade = Grade.B 195 | }, 196 | new Enrollment { 197 | StudentID = students.Single(s => s.LastName == "Alonso").ID, 198 | CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 199 | Grade = Grade.B 200 | }, 201 | new Enrollment { 202 | StudentID = students.Single(s => s.LastName == "Alonso").ID, 203 | CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 204 | Grade = Grade.B 205 | }, 206 | new Enrollment { 207 | StudentID = students.Single(s => s.LastName == "Alonso").ID, 208 | CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 209 | Grade = Grade.B 210 | }, 211 | new Enrollment { 212 | StudentID = students.Single(s => s.LastName == "Anand").ID, 213 | CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID 214 | }, 215 | new Enrollment { 216 | StudentID = students.Single(s => s.LastName == "Anand").ID, 217 | CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID, 218 | Grade = Grade.B 219 | }, 220 | new Enrollment { 221 | StudentID = students.Single(s => s.LastName == "Barzdukas").ID, 222 | CourseID = courses.Single(c => c.Title == "Chemistry").CourseID, 223 | Grade = Grade.B 224 | }, 225 | new Enrollment { 226 | StudentID = students.Single(s => s.LastName == "Li").ID, 227 | CourseID = courses.Single(c => c.Title == "Composition").CourseID, 228 | Grade = Grade.B 229 | }, 230 | new Enrollment { 231 | StudentID = students.Single(s => s.LastName == "Justice").ID, 232 | CourseID = courses.Single(c => c.Title == "Literature").CourseID, 233 | Grade = Grade.B 234 | } 235 | }; 236 | 237 | foreach (Enrollment e in enrollments) 238 | { 239 | var enrollmentInDataBase = context.Enrollments.Where( 240 | s => 241 | s.Student.ID == e.StudentID && 242 | s.Course.CourseID == e.CourseID).SingleOrDefault(); 243 | if (enrollmentInDataBase == null) 244 | { 245 | context.Enrollments.Add(e); 246 | } 247 | } 248 | context.SaveChanges(); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Data/Factory/SchoolContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data.Factory 4 | { 5 | public class SchoolContextFactory : IDbContextFactory 6 | { 7 | private readonly bool _isLazy; 8 | 9 | public SchoolContextFactory() : this(false) 10 | { 11 | } 12 | 13 | public SchoolContextFactory(bool isLazy) 14 | { 15 | _isLazy = isLazy; 16 | } 17 | 18 | public SchoolContext Create(DbContextFactoryOptions options) 19 | { 20 | var dbContextOptionsBuilder = new DbContextOptionsBuilder(); 21 | 22 | dbContextOptionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity;Trusted_Connection=True;MultipleActiveResultSets=true"); 23 | 24 | // LazyLoading specific 25 | if (_isLazy) 26 | { 27 | dbContextOptionsBuilder.UseLazyLoading(); 28 | } 29 | 30 | // Build DbContext 31 | return new SchoolContext(dbContextOptionsBuilder.Options); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Data/SchoolContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data 4 | { 5 | public class SchoolContext : DbContext 6 | { 7 | public SchoolContext(DbContextOptions options) : base(options) 8 | { 9 | } 10 | 11 | public DbSet Courses { get; set; } 12 | public DbSet Enrollments { get; set; } 13 | public DbSet Students { get; set; } 14 | public DbSet Departments { get; set; } 15 | public DbSet Instructors { get; set; } 16 | public DbSet OfficeAssignments { get; set; } 17 | public DbSet CourseAssignments { get; set; } 18 | public DbSet People { get; set; } 19 | 20 | protected override void OnModelCreating(ModelBuilder modelBuilder) 21 | { 22 | modelBuilder.Entity(); 23 | modelBuilder.Entity(); 24 | modelBuilder.Entity(); 25 | modelBuilder.Entity(); 26 | modelBuilder.Entity(); 27 | modelBuilder.Entity(); 28 | modelBuilder.Entity(); 29 | modelBuilder.Entity() 30 | .HasDiscriminator("Discriminator") 31 | .HasValue(nameof(Student)) 32 | .HasValue(nameof(Instructor)); 33 | 34 | modelBuilder.Entity() 35 | .HasKey(c => new { c.CourseID, c.InstructorID }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Microsoft.EntityFrameworkCore.LazyLoading.Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp1.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Migrations/20170327180841_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data; 7 | 8 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Migrations 9 | { 10 | [DbContext(typeof(SchoolContext))] 11 | [Migration("20170327180841_Initial")] 12 | partial class Initial 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.1.1") 18 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 19 | 20 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Course", b => 21 | { 22 | b.Property("CourseID"); 23 | 24 | b.Property("Credits"); 25 | 26 | b.Property("DepartmentID"); 27 | 28 | b.Property("Title") 29 | .HasMaxLength(50); 30 | 31 | b.HasKey("CourseID"); 32 | 33 | b.HasIndex("DepartmentID"); 34 | 35 | b.ToTable("Courses"); 36 | }); 37 | 38 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.CourseAssignment", b => 39 | { 40 | b.Property("CourseID"); 41 | 42 | b.Property("InstructorID"); 43 | 44 | b.HasKey("CourseID", "InstructorID"); 45 | 46 | b.HasIndex("InstructorID"); 47 | 48 | b.ToTable("CourseAssignments"); 49 | }); 50 | 51 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Department", b => 52 | { 53 | b.Property("DepartmentID") 54 | .ValueGeneratedOnAdd(); 55 | 56 | b.Property("Budget") 57 | .HasColumnType("money"); 58 | 59 | b.Property("InstructorID"); 60 | 61 | b.Property("Name") 62 | .HasMaxLength(50); 63 | 64 | b.Property("RowVersion") 65 | .IsConcurrencyToken() 66 | .ValueGeneratedOnAddOrUpdate(); 67 | 68 | b.Property("StartDate"); 69 | 70 | b.HasKey("DepartmentID"); 71 | 72 | b.HasIndex("InstructorID"); 73 | 74 | b.ToTable("Departments"); 75 | }); 76 | 77 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Enrollment", b => 78 | { 79 | b.Property("EnrollmentID") 80 | .ValueGeneratedOnAdd(); 81 | 82 | b.Property("CourseID"); 83 | 84 | b.Property("Grade"); 85 | 86 | b.Property("StudentID"); 87 | 88 | b.HasKey("EnrollmentID"); 89 | 90 | b.HasIndex("CourseID"); 91 | 92 | b.HasIndex("StudentID"); 93 | 94 | b.ToTable("Enrollments"); 95 | }); 96 | 97 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.OfficeAssignment", b => 98 | { 99 | b.Property("InstructorID"); 100 | 101 | b.Property("Location") 102 | .HasMaxLength(50); 103 | 104 | b.HasKey("InstructorID"); 105 | 106 | b.ToTable("OfficeAssignments"); 107 | }); 108 | 109 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Person", b => 110 | { 111 | b.Property("ID") 112 | .ValueGeneratedOnAdd(); 113 | 114 | b.Property("Discriminator") 115 | .IsRequired(); 116 | 117 | b.Property("FirstMidName") 118 | .IsRequired() 119 | .HasColumnName("FirstName") 120 | .HasMaxLength(50); 121 | 122 | b.Property("LastName") 123 | .IsRequired() 124 | .HasMaxLength(50); 125 | 126 | b.HasKey("ID"); 127 | 128 | b.ToTable("People"); 129 | 130 | b.HasDiscriminator("Discriminator").HasValue("Person"); 131 | }); 132 | 133 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Instructor", b => 134 | { 135 | b.HasBaseType("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Person"); 136 | 137 | b.Property("HireDate"); 138 | 139 | b.ToTable("Instructor"); 140 | 141 | b.HasDiscriminator().HasValue("Instructor"); 142 | }); 143 | 144 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Student", b => 145 | { 146 | b.HasBaseType("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Person"); 147 | 148 | b.Property("EnrollmentDate"); 149 | 150 | b.ToTable("Student"); 151 | 152 | b.HasDiscriminator().HasValue("Student"); 153 | }); 154 | 155 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Course", b => 156 | { 157 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Department", "Department") 158 | .WithMany("Courses") 159 | .HasForeignKey("DepartmentID") 160 | .OnDelete(DeleteBehavior.Cascade); 161 | }); 162 | 163 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.CourseAssignment", b => 164 | { 165 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Course", "Course") 166 | .WithMany("CourseAssignments") 167 | .HasForeignKey("CourseID") 168 | .OnDelete(DeleteBehavior.Cascade); 169 | 170 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Instructor", "Instructor") 171 | .WithMany("CourseAssignments") 172 | .HasForeignKey("InstructorID") 173 | .OnDelete(DeleteBehavior.Cascade); 174 | }); 175 | 176 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Department", b => 177 | { 178 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Instructor", "Administrator") 179 | .WithMany() 180 | .HasForeignKey("InstructorID"); 181 | }); 182 | 183 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Enrollment", b => 184 | { 185 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Course", "Course") 186 | .WithMany("Enrollments") 187 | .HasForeignKey("CourseID") 188 | .OnDelete(DeleteBehavior.Cascade); 189 | 190 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Student", "Student") 191 | .WithMany("Enrollments") 192 | .HasForeignKey("StudentID") 193 | .OnDelete(DeleteBehavior.Cascade); 194 | }); 195 | 196 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.OfficeAssignment", b => 197 | { 198 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Instructor", "Instructor") 199 | .WithOne("OfficeAssignment") 200 | .HasForeignKey("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.OfficeAssignment", "InstructorID") 201 | .OnDelete(DeleteBehavior.Cascade); 202 | }); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Migrations/20170327180841_Initial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | 6 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Migrations 7 | { 8 | public partial class Initial : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "People", 14 | columns: table => new 15 | { 16 | ID = table.Column(nullable: false) 17 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 18 | Discriminator = table.Column(nullable: false), 19 | FirstName = table.Column(maxLength: 50, nullable: false), 20 | LastName = table.Column(maxLength: 50, nullable: false), 21 | HireDate = table.Column(nullable: true), 22 | EnrollmentDate = table.Column(nullable: true) 23 | }, 24 | constraints: table => 25 | { 26 | table.PrimaryKey("PK_People", x => x.ID); 27 | }); 28 | 29 | migrationBuilder.CreateTable( 30 | name: "Departments", 31 | columns: table => new 32 | { 33 | DepartmentID = table.Column(nullable: false) 34 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 35 | Budget = table.Column(type: "money", nullable: false), 36 | InstructorID = table.Column(nullable: true), 37 | Name = table.Column(maxLength: 50, nullable: true), 38 | RowVersion = table.Column(rowVersion: true, nullable: true), 39 | StartDate = table.Column(nullable: false) 40 | }, 41 | constraints: table => 42 | { 43 | table.PrimaryKey("PK_Departments", x => x.DepartmentID); 44 | table.ForeignKey( 45 | name: "FK_Departments_People_InstructorID", 46 | column: x => x.InstructorID, 47 | principalTable: "People", 48 | principalColumn: "ID", 49 | onDelete: ReferentialAction.Restrict); 50 | }); 51 | 52 | migrationBuilder.CreateTable( 53 | name: "OfficeAssignments", 54 | columns: table => new 55 | { 56 | InstructorID = table.Column(nullable: false), 57 | Location = table.Column(maxLength: 50, nullable: true) 58 | }, 59 | constraints: table => 60 | { 61 | table.PrimaryKey("PK_OfficeAssignments", x => x.InstructorID); 62 | table.ForeignKey( 63 | name: "FK_OfficeAssignments_People_InstructorID", 64 | column: x => x.InstructorID, 65 | principalTable: "People", 66 | principalColumn: "ID", 67 | onDelete: ReferentialAction.Cascade); 68 | }); 69 | 70 | migrationBuilder.CreateTable( 71 | name: "Courses", 72 | columns: table => new 73 | { 74 | CourseID = table.Column(nullable: false), 75 | Credits = table.Column(nullable: false), 76 | DepartmentID = table.Column(nullable: false), 77 | Title = table.Column(maxLength: 50, nullable: true) 78 | }, 79 | constraints: table => 80 | { 81 | table.PrimaryKey("PK_Courses", x => x.CourseID); 82 | table.ForeignKey( 83 | name: "FK_Courses_Departments_DepartmentID", 84 | column: x => x.DepartmentID, 85 | principalTable: "Departments", 86 | principalColumn: "DepartmentID", 87 | onDelete: ReferentialAction.Cascade); 88 | }); 89 | 90 | migrationBuilder.CreateTable( 91 | name: "CourseAssignments", 92 | columns: table => new 93 | { 94 | CourseID = table.Column(nullable: false), 95 | InstructorID = table.Column(nullable: false) 96 | }, 97 | constraints: table => 98 | { 99 | table.PrimaryKey("PK_CourseAssignments", x => new { x.CourseID, x.InstructorID }); 100 | table.ForeignKey( 101 | name: "FK_CourseAssignments_Courses_CourseID", 102 | column: x => x.CourseID, 103 | principalTable: "Courses", 104 | principalColumn: "CourseID", 105 | onDelete: ReferentialAction.Cascade); 106 | table.ForeignKey( 107 | name: "FK_CourseAssignments_People_InstructorID", 108 | column: x => x.InstructorID, 109 | principalTable: "People", 110 | principalColumn: "ID", 111 | onDelete: ReferentialAction.Cascade); 112 | }); 113 | 114 | migrationBuilder.CreateTable( 115 | name: "Enrollments", 116 | columns: table => new 117 | { 118 | EnrollmentID = table.Column(nullable: false) 119 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 120 | CourseID = table.Column(nullable: false), 121 | Grade = table.Column(nullable: true), 122 | StudentID = table.Column(nullable: false) 123 | }, 124 | constraints: table => 125 | { 126 | table.PrimaryKey("PK_Enrollments", x => x.EnrollmentID); 127 | table.ForeignKey( 128 | name: "FK_Enrollments_Courses_CourseID", 129 | column: x => x.CourseID, 130 | principalTable: "Courses", 131 | principalColumn: "CourseID", 132 | onDelete: ReferentialAction.Cascade); 133 | table.ForeignKey( 134 | name: "FK_Enrollments_People_StudentID", 135 | column: x => x.StudentID, 136 | principalTable: "People", 137 | principalColumn: "ID", 138 | onDelete: ReferentialAction.Cascade); 139 | }); 140 | 141 | migrationBuilder.CreateIndex( 142 | name: "IX_Courses_DepartmentID", 143 | table: "Courses", 144 | column: "DepartmentID"); 145 | 146 | migrationBuilder.CreateIndex( 147 | name: "IX_CourseAssignments_InstructorID", 148 | table: "CourseAssignments", 149 | column: "InstructorID"); 150 | 151 | migrationBuilder.CreateIndex( 152 | name: "IX_Departments_InstructorID", 153 | table: "Departments", 154 | column: "InstructorID"); 155 | 156 | migrationBuilder.CreateIndex( 157 | name: "IX_Enrollments_CourseID", 158 | table: "Enrollments", 159 | column: "CourseID"); 160 | 161 | migrationBuilder.CreateIndex( 162 | name: "IX_Enrollments_StudentID", 163 | table: "Enrollments", 164 | column: "StudentID"); 165 | } 166 | 167 | protected override void Down(MigrationBuilder migrationBuilder) 168 | { 169 | migrationBuilder.DropTable( 170 | name: "CourseAssignments"); 171 | 172 | migrationBuilder.DropTable( 173 | name: "Enrollments"); 174 | 175 | migrationBuilder.DropTable( 176 | name: "OfficeAssignments"); 177 | 178 | migrationBuilder.DropTable( 179 | name: "Courses"); 180 | 181 | migrationBuilder.DropTable( 182 | name: "Departments"); 183 | 184 | migrationBuilder.DropTable( 185 | name: "People"); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Migrations/SchoolContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data; 7 | 8 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Migrations 9 | { 10 | [DbContext(typeof(SchoolContext))] 11 | partial class SchoolContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | modelBuilder 16 | .HasAnnotation("ProductVersion", "1.1.1") 17 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 18 | 19 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Course", b => 20 | { 21 | b.Property("CourseID"); 22 | 23 | b.Property("Credits"); 24 | 25 | b.Property("DepartmentID"); 26 | 27 | b.Property("Title") 28 | .HasMaxLength(50); 29 | 30 | b.HasKey("CourseID"); 31 | 32 | b.HasIndex("DepartmentID"); 33 | 34 | b.ToTable("Courses"); 35 | }); 36 | 37 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.CourseAssignment", b => 38 | { 39 | b.Property("CourseID"); 40 | 41 | b.Property("InstructorID"); 42 | 43 | b.HasKey("CourseID", "InstructorID"); 44 | 45 | b.HasIndex("InstructorID"); 46 | 47 | b.ToTable("CourseAssignments"); 48 | }); 49 | 50 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Department", b => 51 | { 52 | b.Property("DepartmentID") 53 | .ValueGeneratedOnAdd(); 54 | 55 | b.Property("Budget") 56 | .HasColumnType("money"); 57 | 58 | b.Property("InstructorID"); 59 | 60 | b.Property("Name") 61 | .HasMaxLength(50); 62 | 63 | b.Property("RowVersion") 64 | .IsConcurrencyToken() 65 | .ValueGeneratedOnAddOrUpdate(); 66 | 67 | b.Property("StartDate"); 68 | 69 | b.HasKey("DepartmentID"); 70 | 71 | b.HasIndex("InstructorID"); 72 | 73 | b.ToTable("Departments"); 74 | }); 75 | 76 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Enrollment", b => 77 | { 78 | b.Property("EnrollmentID") 79 | .ValueGeneratedOnAdd(); 80 | 81 | b.Property("CourseID"); 82 | 83 | b.Property("Grade"); 84 | 85 | b.Property("StudentID"); 86 | 87 | b.HasKey("EnrollmentID"); 88 | 89 | b.HasIndex("CourseID"); 90 | 91 | b.HasIndex("StudentID"); 92 | 93 | b.ToTable("Enrollments"); 94 | }); 95 | 96 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.OfficeAssignment", b => 97 | { 98 | b.Property("InstructorID"); 99 | 100 | b.Property("Location") 101 | .HasMaxLength(50); 102 | 103 | b.HasKey("InstructorID"); 104 | 105 | b.ToTable("OfficeAssignments"); 106 | }); 107 | 108 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Person", b => 109 | { 110 | b.Property("ID") 111 | .ValueGeneratedOnAdd(); 112 | 113 | b.Property("Discriminator") 114 | .IsRequired(); 115 | 116 | b.Property("FirstMidName") 117 | .IsRequired() 118 | .HasColumnName("FirstName") 119 | .HasMaxLength(50); 120 | 121 | b.Property("LastName") 122 | .IsRequired() 123 | .HasMaxLength(50); 124 | 125 | b.HasKey("ID"); 126 | 127 | b.ToTable("People"); 128 | 129 | b.HasDiscriminator("Discriminator").HasValue("Person"); 130 | }); 131 | 132 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Instructor", b => 133 | { 134 | b.HasBaseType("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Person"); 135 | 136 | b.Property("HireDate"); 137 | 138 | b.ToTable("Instructor"); 139 | 140 | b.HasDiscriminator().HasValue("Instructor"); 141 | }); 142 | 143 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Student", b => 144 | { 145 | b.HasBaseType("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Person"); 146 | 147 | b.Property("EnrollmentDate"); 148 | 149 | b.ToTable("Student"); 150 | 151 | b.HasDiscriminator().HasValue("Student"); 152 | }); 153 | 154 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Course", b => 155 | { 156 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Department", "Department") 157 | .WithMany("Courses") 158 | .HasForeignKey("DepartmentID") 159 | .OnDelete(DeleteBehavior.Cascade); 160 | }); 161 | 162 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.CourseAssignment", b => 163 | { 164 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Course", "Course") 165 | .WithMany("CourseAssignments") 166 | .HasForeignKey("CourseID") 167 | .OnDelete(DeleteBehavior.Cascade); 168 | 169 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Instructor", "Instructor") 170 | .WithMany("CourseAssignments") 171 | .HasForeignKey("InstructorID") 172 | .OnDelete(DeleteBehavior.Cascade); 173 | }); 174 | 175 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Department", b => 176 | { 177 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Instructor", "Administrator") 178 | .WithMany() 179 | .HasForeignKey("InstructorID"); 180 | }); 181 | 182 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Enrollment", b => 183 | { 184 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Course", "Course") 185 | .WithMany("Enrollments") 186 | .HasForeignKey("CourseID") 187 | .OnDelete(DeleteBehavior.Cascade); 188 | 189 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Student", "Student") 190 | .WithMany("Enrollments") 191 | .HasForeignKey("StudentID") 192 | .OnDelete(DeleteBehavior.Cascade); 193 | }); 194 | 195 | modelBuilder.Entity("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.OfficeAssignment", b => 196 | { 197 | b.HasOne("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.Instructor", "Instructor") 198 | .WithOne("OfficeAssignment") 199 | .HasForeignKey("Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models.OfficeAssignment", "InstructorID") 200 | .OnDelete(DeleteBehavior.Cascade); 201 | }); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Models/Course.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models 6 | { 7 | public class Course 8 | { 9 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 10 | [Display(Name = "Number")] 11 | public int CourseID { get; set; } 12 | 13 | [StringLength(50, MinimumLength = 3)] 14 | public string Title { get; set; } 15 | 16 | [Range(0, 5)] 17 | public int Credits { get; set; } 18 | 19 | public int DepartmentID { get; set; } 20 | 21 | private LazyReference _departmentLazy = new LazyReference(); 22 | public Department Department 23 | { 24 | get 25 | { 26 | return _departmentLazy.GetValue(this); 27 | } 28 | set 29 | { 30 | _departmentLazy.SetValue(value); 31 | } 32 | } 33 | public ICollection Enrollments { get; set; } 34 | public ICollection CourseAssignments { get; set; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Models/CourseAssignment.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models 2 | { 3 | public class CourseAssignment 4 | { 5 | public int InstructorID { get; set; } 6 | public int CourseID { get; set; } 7 | private LazyReference _instructorLazy = new LazyReference(); 8 | public Instructor Instructor 9 | { 10 | get 11 | { 12 | return _instructorLazy.GetValue(this); 13 | } 14 | set 15 | { 16 | _instructorLazy.SetValue(value); 17 | } 18 | } 19 | private LazyReference _courseLazy = new LazyReference(); 20 | public Course Course 21 | { 22 | get 23 | { 24 | return _courseLazy.GetValue(this); 25 | } 26 | set 27 | { 28 | _courseLazy.SetValue(value); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Models/Department.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models 7 | { 8 | public class Department 9 | { 10 | public int DepartmentID { get; set; } 11 | 12 | [StringLength(50, MinimumLength = 3)] 13 | public string Name { get; set; } 14 | 15 | [DataType(DataType.Currency)] 16 | [Column(TypeName = "money")] 17 | public decimal Budget { get; set; } 18 | 19 | [DataType(DataType.Date)] 20 | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] 21 | [Display(Name = "Start Date")] 22 | public DateTime StartDate { get; set; } 23 | 24 | public int? InstructorID { get; set; } 25 | 26 | [Timestamp] 27 | public byte[] RowVersion { get; set; } 28 | private LazyReference _administratorLazy = new LazyReference(); 29 | 30 | public Instructor Administrator 31 | { 32 | get 33 | { 34 | return _administratorLazy.GetValue(this); 35 | } 36 | set 37 | { 38 | _administratorLazy.SetValue(value); 39 | } 40 | } 41 | public ICollection Courses { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Models/Enrollment.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models 4 | { 5 | public enum Grade 6 | { 7 | A, B, C, D, F 8 | } 9 | 10 | public class Enrollment 11 | { 12 | public int EnrollmentID { get; set; } 13 | public int CourseID { get; set; } 14 | public int StudentID { get; set; } 15 | [DisplayFormat(NullDisplayText = "No grade")] 16 | public Grade? Grade { get; set; } 17 | 18 | private LazyReference _courseLazy = new LazyReference(); 19 | public Course Course 20 | { 21 | get 22 | { 23 | return _courseLazy.GetValue(this); 24 | } 25 | set 26 | { 27 | _courseLazy.SetValue(value); 28 | } 29 | } 30 | private LazyReference _studentLazy = new LazyReference(); 31 | public Student Student 32 | { 33 | get 34 | { 35 | return _studentLazy.GetValue(this); 36 | } 37 | set 38 | { 39 | _studentLazy.SetValue(value); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Models/Instructor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models 6 | { 7 | public class Instructor : Person 8 | { 9 | [DataType(DataType.Date)] 10 | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] 11 | [Display(Name = "Hire Date")] 12 | public DateTime HireDate { get; set; } 13 | 14 | public ICollection CourseAssignments { get; set; } 15 | private LazyReference _officeAssignmentLazy = new LazyReference(); 16 | public OfficeAssignment OfficeAssignment 17 | { 18 | get 19 | { 20 | return _officeAssignmentLazy.GetValue(this); 21 | } 22 | set 23 | { 24 | _officeAssignmentLazy.SetValue(value); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Models/OfficeAssignment.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models 4 | { 5 | public class OfficeAssignment 6 | { 7 | [Key] 8 | public int InstructorID { get; set; } 9 | [StringLength(50)] 10 | [Display(Name = "Office Location")] 11 | public string Location { get; set; } 12 | 13 | private LazyReference _instructorLazy = new LazyReference(); 14 | public Instructor Instructor 15 | { 16 | get 17 | { 18 | return _instructorLazy.GetValue(this); 19 | } 20 | set 21 | { 22 | _instructorLazy.SetValue(value); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Models/Person.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models 5 | { 6 | public abstract class Person 7 | { 8 | public int ID { get; set; } 9 | 10 | [Required] 11 | [StringLength(50)] 12 | [Display(Name = "Last Name")] 13 | public string LastName { get; set; } 14 | [Required] 15 | [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")] 16 | [Column("FirstName")] 17 | [Display(Name = "First Name")] 18 | public string FirstMidName { get; set; } 19 | 20 | [Display(Name = "Full Name")] 21 | public string FullName 22 | { 23 | get 24 | { 25 | return LastName + ", " + FirstMidName; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Models/Student.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models 6 | { 7 | public class Student : Person 8 | { 9 | [DataType(DataType.Date)] 10 | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] 11 | [Display(Name = "Enrollment Date")] 12 | public DateTime EnrollmentDate { get; set; } 13 | 14 | public ICollection Enrollments { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/Microsoft.EntityFrameworkCore.LazyLoading.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | using Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data; 3 | using Microsoft.EntityFrameworkCore.LazyLoading.Sample.Data.Factory; 4 | using Microsoft.EntityFrameworkCore.LazyLoading.Sample.Models; 5 | using System; 6 | using System.Linq; 7 | 8 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Sample 9 | { 10 | class Program 11 | { 12 | // ReSharper disable once UnusedMember.Local 13 | // ReSharper disable once UnusedParameter.Local 14 | static void Main(string[] args) 15 | { 16 | var lazyFactory = new SchoolContextFactory(true); 17 | var defaultFactory = new SchoolContextFactory(false); 18 | var factoryOptions = new DbContextFactoryOptions(); 19 | 20 | using (var dbContext = lazyFactory.Create(factoryOptions)) 21 | { 22 | DbInitializer.Initialize(dbContext); 23 | } 24 | 25 | using (var dbContext = lazyFactory.Create(factoryOptions)) 26 | { 27 | var student = dbContext.Students.First(); 28 | var deptName = student.Enrollments.First().Course.Department.Name; 29 | Console.WriteLine($"Department name is '{deptName}'. LazyLoading enabled, no need to call .Include()."); 30 | 31 | Console.WriteLine("Querying instructor with an OfficeAssignment."); 32 | var instructorWithOfficeAssignment = dbContext.Instructors.First(x => x.OfficeAssignment != null); 33 | Console.WriteLine($"Instructor has {(instructorWithOfficeAssignment.OfficeAssignment == null ? "no (WTF? We just queried instructors with OfficeAssignment!)" : "an")} OfficeAssignment."); 34 | } 35 | 36 | using (var dbContext = lazyFactory.Create(factoryOptions)) 37 | { 38 | var instructorAbercrombie = dbContext.Set().First(x => x.LastName == "Abercrombie"); 39 | 40 | var coursesOfinstructorAbercrombie = instructorAbercrombie.CourseAssignments.Select(x => x.Course); 41 | 42 | var courseCount = coursesOfinstructorAbercrombie.Count(); 43 | 44 | Console.WriteLine($"Instructor Abercrombie has {courseCount} courses."); 45 | } 46 | 47 | using (var dbContext = defaultFactory.Create(factoryOptions)) 48 | { 49 | var student = dbContext.Students.First(); 50 | try 51 | { 52 | // ReSharper disable once UnusedVariable 53 | var deptName = student.Enrollments.First().Course.Department.Name; 54 | Console.WriteLine("Oops... Something didn't work. LazyLoading should not be enabled by default."); 55 | } 56 | catch (ArgumentNullException) 57 | { 58 | Console.WriteLine($"{nameof(ArgumentNullException)} occured because LazyLoading was not enabled and .Include() was not called."); 59 | } 60 | 61 | Console.WriteLine("Querying instructor with an OfficeAssignment."); 62 | var instructorWithOfficeAssignment = dbContext.Instructors.First(x => x.OfficeAssignment != null); 63 | Console.WriteLine($"Instructor has {(instructorWithOfficeAssignment.OfficeAssignment == null ? "no (WTF? We just queried instructors with OfficeAssignment!)" : "an")} OfficeAssignment."); 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/Exceptions/LazyLoadingConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Exceptions 4 | { 5 | public class LazyLoadingConfigurationException : Exception 6 | { 7 | public LazyLoadingConfigurationException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/Infrastructure/Internal/LazyLoadingOptionsExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | using Microsoft.EntityFrameworkCore.LazyLoading.Internal; 3 | using Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal; 4 | using Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal; 5 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 6 | using Microsoft.EntityFrameworkCore.Query.Internal; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Infrastructure.Internal 10 | { 11 | public class LazyLoadingOptionsExtension : IDbContextOptionsExtension 12 | { 13 | public LazyLoadingOptionsExtension() 14 | { 15 | } 16 | 17 | // NB: When adding new options, make sure to update the copy ctor below. 18 | 19 | // ReSharper disable once UnusedParameter.Local 20 | public LazyLoadingOptionsExtension(LazyLoadingOptionsExtension copyFrom) 21 | { 22 | } 23 | 24 | public void ApplyServices(IServiceCollection services) 25 | { 26 | services.AddScoped(); 27 | services.AddScoped(); 28 | services.AddScoped(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/Internal/ConcurrencyDetector.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Internal; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Internal 8 | { 9 | /// 10 | /// This API supports the Entity Framework Core infrastructure and is not intended to be used 11 | /// directly from your code. This API may change or be removed in future releases. 12 | /// 13 | public class ConcurrencyDetector : IConcurrencyDetector, IDisposable 14 | { 15 | private readonly IDisposable _disposer; 16 | 17 | private SemaphoreSlim _semaphore = new SemaphoreSlim(1); 18 | 19 | private int _inCriticalSection; 20 | 21 | /// 22 | /// This API supports the Entity Framework Core infrastructure and is not intended to be used 23 | /// directly from your code. This API may change or be removed in future releases. 24 | /// 25 | public ConcurrencyDetector() 26 | { 27 | _disposer = new Disposer(this); 28 | } 29 | 30 | /// 31 | /// This API supports the Entity Framework Core infrastructure and is not intended to be used 32 | /// directly from your code. This API may change or be removed in future releases. 33 | /// 34 | public virtual IDisposable EnterCriticalSection() 35 | { 36 | if (Interlocked.CompareExchange(ref _inCriticalSection, 1, 0) == 1) 37 | { 38 | throw new InvalidOperationException(CoreStrings.ConcurrentMethodInvocation); 39 | } 40 | 41 | return _disposer; 42 | } 43 | 44 | private void ExitCriticalSection() 45 | { 46 | Debug.Assert(_inCriticalSection == 1, "Expected to be in a critical section"); 47 | 48 | _inCriticalSection = 0; 49 | } 50 | 51 | /// 52 | /// This API supports the Entity Framework Core infrastructure and is not intended to be used 53 | /// directly from your code. This API may change or be removed in future releases. 54 | /// 55 | public virtual async Task EnterCriticalSectionAsync(CancellationToken cancellationToken) 56 | { 57 | await _semaphore.WaitAsync(cancellationToken); 58 | 59 | return new AsyncDisposer(EnterCriticalSection(), this); 60 | } 61 | 62 | private struct AsyncDisposer : IDisposable 63 | { 64 | private readonly IDisposable _disposable; 65 | private readonly ConcurrencyDetector _concurrencyDetector; 66 | 67 | public AsyncDisposer(IDisposable disposable, ConcurrencyDetector concurrencyDetector) 68 | { 69 | _disposable = disposable; 70 | _concurrencyDetector = concurrencyDetector; 71 | } 72 | 73 | public void Dispose() 74 | { 75 | _disposable.Dispose(); 76 | 77 | if (_concurrencyDetector._semaphore == null) 78 | { 79 | throw new ObjectDisposedException(GetType().ShortDisplayName(), CoreStrings.ContextDisposed); 80 | } 81 | 82 | _concurrencyDetector._semaphore.Release(); 83 | } 84 | } 85 | 86 | private struct Disposer : IDisposable 87 | { 88 | private readonly ConcurrencyDetector _concurrencyDetector; 89 | 90 | public Disposer(ConcurrencyDetector concurrencyDetector) 91 | { 92 | _concurrencyDetector = concurrencyDetector; 93 | } 94 | 95 | public void Dispose() => _concurrencyDetector.ExitCriticalSection(); 96 | } 97 | 98 | /// 99 | /// This API supports the Entity Framework Core infrastructure and is not intended to be used 100 | /// directly from your code. This API may change or be removed in future releases. 101 | /// 102 | public virtual void Dispose() 103 | { 104 | _semaphore?.Dispose(); 105 | _semaphore = null; 106 | } 107 | 108 | public bool IsInOperation() 109 | { 110 | return _inCriticalSection > 0; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/Internal/IConcurrencyDetector.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Internal 2 | { 3 | public interface IConcurrencyDetector : EntityFrameworkCore.Internal.IConcurrencyDetector 4 | { 5 | bool IsInOperation(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/LazyCollection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | using Microsoft.EntityFrameworkCore.LazyLoading.Exceptions; 3 | using Microsoft.EntityFrameworkCore.LazyLoading.Internal; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace Microsoft.EntityFrameworkCore.LazyLoading 9 | { 10 | public sealed class LazyCollection : IList 11 | where T : class 12 | { 13 | private bool _loaded; 14 | private bool _loading; 15 | private readonly DbContext _ctx; 16 | private readonly string _collectionName; 17 | private readonly object _parent; 18 | private IList _entries = new List(); 19 | 20 | public LazyCollection(DbContext ctx, object parent, string collectionName) 21 | { 22 | _ctx = ctx; 23 | _parent = parent; 24 | _collectionName = collectionName; 25 | } 26 | 27 | private void EnsureLoaded() 28 | { 29 | if (_loaded || _loading) 30 | { 31 | return; 32 | } 33 | 34 | _loading = true; 35 | 36 | try 37 | { 38 | var concurrencyDetector = 39 | _ctx.GetService() as IConcurrencyDetector; 40 | if (concurrencyDetector == null) 41 | { 42 | throw new LazyLoadingConfigurationException( 43 | $"Service of type '{typeof(EntityFrameworkCore.Internal.IConcurrencyDetector).FullName}' must be replaced by a service of type '{typeof(IConcurrencyDetector).FullName}' in order to use LazyLoading"); 44 | } 45 | 46 | if (concurrencyDetector.IsInOperation()) 47 | { 48 | return; 49 | } 50 | 51 | _entries = _ctx 52 | .Entry(_parent) 53 | .Collection(_collectionName) 54 | .Query() 55 | .OfType() 56 | .ToList(); 57 | } 58 | finally 59 | { 60 | _loading = false; 61 | } 62 | 63 | _loaded = true; 64 | } 65 | 66 | IEnumerator IEnumerable.GetEnumerator() 67 | { 68 | EnsureLoaded(); 69 | 70 | return _entries.GetEnumerator(); 71 | } 72 | 73 | IEnumerator IEnumerable.GetEnumerator() 74 | { 75 | return (this as ICollection).GetEnumerator(); 76 | } 77 | 78 | int ICollection.Count 79 | { 80 | get 81 | { 82 | EnsureLoaded(); 83 | return _entries.Count; 84 | } 85 | } 86 | 87 | bool ICollection.IsReadOnly => false; 88 | 89 | void ICollection.Add(T item) 90 | { 91 | EnsureLoaded(); 92 | _entries.Add(item); 93 | } 94 | 95 | void ICollection.Clear() 96 | { 97 | EnsureLoaded(); 98 | _entries.Clear(); 99 | } 100 | 101 | bool ICollection.Contains(T item) 102 | { 103 | EnsureLoaded(); 104 | return _entries.Contains(item); 105 | } 106 | 107 | void ICollection.CopyTo(T[] array, int arrayIndex) 108 | { 109 | EnsureLoaded(); 110 | _entries.CopyTo(array, arrayIndex); 111 | } 112 | 113 | bool ICollection.Remove(T item) 114 | { 115 | EnsureLoaded(); 116 | return _entries.Remove(item); 117 | } 118 | 119 | T IList.this[int index] 120 | { 121 | get 122 | { 123 | EnsureLoaded(); 124 | return _entries[index]; 125 | } 126 | 127 | set 128 | { 129 | EnsureLoaded(); 130 | _entries[index] = value; 131 | } 132 | } 133 | 134 | int IList.IndexOf(T item) 135 | { 136 | EnsureLoaded(); 137 | return _entries.IndexOf(item); 138 | } 139 | 140 | void IList.Insert(int index, T item) 141 | { 142 | EnsureLoaded(); 143 | _entries.Insert(index, item); 144 | } 145 | 146 | void IList.RemoveAt(int index) 147 | { 148 | EnsureLoaded(); 149 | _entries.RemoveAt(index); 150 | } 151 | 152 | public override string ToString() 153 | { 154 | EnsureLoaded(); 155 | return _entries.ToString(); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/LazyLoadingDbContextOptionsBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | using Microsoft.EntityFrameworkCore.LazyLoading.Infrastructure.Internal; 3 | 4 | // ReSharper disable once CheckNamespace 5 | namespace Microsoft.EntityFrameworkCore 6 | { 7 | public static class LazyLoadingDbContextOptionsBuilderExtensions 8 | { 9 | public static DbContextOptionsBuilder UseLazyLoading(this DbContextOptionsBuilder optionsBuilder) 10 | { 11 | var extension = GetOrCreateExtension(optionsBuilder); 12 | ((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(extension); 13 | 14 | return optionsBuilder; 15 | } 16 | 17 | public static DbContextOptionsBuilder UseLazyLoading( 18 | this DbContextOptionsBuilder optionsBuilder) 19 | where TContext : DbContext 20 | => (DbContextOptionsBuilder) UseLazyLoading((DbContextOptionsBuilder) optionsBuilder); 21 | 22 | private static LazyLoadingOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder) 23 | { 24 | var existing = optionsBuilder.Options.FindExtension(); 25 | return existing != null 26 | ? new LazyLoadingOptionsExtension(existing) 27 | : new LazyLoadingOptionsExtension(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/LazyReference.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using Microsoft.EntityFrameworkCore.LazyLoading.Exceptions; 4 | using Microsoft.EntityFrameworkCore.LazyLoading.Internal; 5 | 6 | namespace Microsoft.EntityFrameworkCore.LazyLoading 7 | { 8 | public sealed class LazyReference 9 | where T : class 10 | { 11 | private bool _isLoading; 12 | 13 | private bool _isLoaded; 14 | 15 | private T _value; 16 | 17 | private DbContext _ctx; 18 | 19 | public void SetContext(DbContext ctx) 20 | { 21 | _ctx = ctx; 22 | } 23 | 24 | public void SetValue(T value) 25 | { 26 | _value = value; 27 | _isLoaded = true; 28 | } 29 | 30 | public T GetValue(object parent, [CallerMemberName] string referenceName = null) 31 | { 32 | if (_ctx == null || _isLoaded || _isLoading) 33 | { 34 | return _value; 35 | } 36 | 37 | _isLoading = true; 38 | 39 | try 40 | { 41 | var concurrencyDetector = 42 | _ctx.GetService() as IConcurrencyDetector; 43 | if (concurrencyDetector == null) 44 | { 45 | throw new LazyLoadingConfigurationException( 46 | $"Service of type '{typeof(EntityFrameworkCore.Internal.IConcurrencyDetector).FullName}' must be replaced by a service of type '{typeof(IConcurrencyDetector).FullName}' in order to use LazyLoading"); 47 | } 48 | 49 | if (concurrencyDetector.IsInOperation()) 50 | { 51 | return _value; 52 | } 53 | 54 | _ctx.Entry(parent).Reference(referenceName).Load(); 55 | _isLoaded = true; 56 | } 57 | finally 58 | { 59 | _isLoading = false; 60 | } 61 | 62 | return _value; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/Metadata/Internal/ILazyLoadingEntityMaterializerSource.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal 2 | { 3 | public interface ILazyLoadingEntityMaterializerSource 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/Metadata/Internal/LazyLoadingEntityMaterializerSource.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using Microsoft.EntityFrameworkCore.Internal; 9 | 10 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Metadata.Internal 11 | { 12 | public class LazyLoadingEntityMaterializerSource : EntityMaterializerSource, ILazyLoadingEntityMaterializerSource 13 | { 14 | private readonly ICurrentDbContext _currentDbContext; 15 | 16 | public LazyLoadingEntityMaterializerSource(ICurrentDbContext currentDbContext) 17 | { 18 | _currentDbContext = currentDbContext; 19 | } 20 | 21 | public override Expression CreateMaterializeExpression(IEntityType entityType, Expression valueBufferExpression, int[] indexMap = null) 22 | { 23 | var expr = base.CreateMaterializeExpression(entityType, valueBufferExpression, indexMap); 24 | 25 | var blockExpr = expr as BlockExpression; 26 | 27 | Debug.Assert(blockExpr != null, "First Expression was not a BlockExpression."); 28 | 29 | var constructExpr = blockExpr.Expressions[0] as BinaryExpression; 30 | 31 | Debug.Assert(constructExpr != null, "First Expression of BlockExpression was not a BinaryExpression."); 32 | 33 | var instanceExpr = constructExpr.Left; 34 | 35 | var newExpressions = blockExpr.Expressions.Take(blockExpr.Expressions.Count - 1).ToList(); 36 | 37 | var navigations = entityType.GetNavigations(); 38 | 39 | // Search LazyCollection navigations 40 | foreach (var navigation in navigations) 41 | { 42 | var navigationTypeInfo = navigation.ClrType.GetTypeInfo(); 43 | if (navigationTypeInfo.IsGenericType && navigationTypeInfo.GenericTypeArguments.Length == 1 && navigationTypeInfo.GenericTypeArguments[0].GetTypeInfo().IsClass) 44 | { 45 | if (navigationTypeInfo.IsAssignableFrom(typeof(LazyCollection<>).MakeGenericType(navigationTypeInfo.GenericTypeArguments).GetTypeInfo())) 46 | { 47 | var createLazyCollectionConstructExpression = CreateLazyCollectionConstructExpression(navigationTypeInfo.GenericTypeArguments[0], _currentDbContext.Context, instanceExpr, navigation.Name); 48 | var propertyOrFieldExpr = Expression.PropertyOrField(instanceExpr, navigation.Name); 49 | var assignExpr = Expression.Assign(propertyOrFieldExpr, createLazyCollectionConstructExpression); 50 | newExpressions.Add(assignExpr); 51 | } 52 | } 53 | } 54 | 55 | 56 | // Search LazyReference fields 57 | foreach (var field in entityType.ClrType.GetTypeInfo().DeclaredFields) 58 | { 59 | var fieldTypeInfo = field.FieldType.GetTypeInfo(); 60 | if (fieldTypeInfo.IsGenericType && fieldTypeInfo.GenericTypeArguments.Length == 1 && fieldTypeInfo.GenericTypeArguments[0].GetTypeInfo().IsClass) 61 | { 62 | if (fieldTypeInfo.IsAssignableFrom(typeof(LazyReference<>).MakeGenericType(fieldTypeInfo.GenericTypeArguments).GetTypeInfo())) 63 | { 64 | var fieldExpr = Expression.Field(instanceExpr, field.Name); 65 | var setDbContextMethodCallExpr = Expression.Call(fieldExpr, nameof(LazyReference.SetContext), new Type[] { }, Expression.Constant(_currentDbContext.Context)); 66 | newExpressions.Add(setDbContextMethodCallExpr); 67 | } 68 | } 69 | } 70 | 71 | newExpressions.Add(blockExpr.Expressions.Last()); 72 | 73 | return Expression.Block(blockExpr.Variables, newExpressions); 74 | } 75 | 76 | private Expression CreateLazyCollectionConstructExpression(Type itemsType, DbContext ctx, Expression parentExpr, string collectionName) 77 | { 78 | return CreateLazyCollectionConstructExpression(itemsType, Expression.Constant(ctx), parentExpr, Expression.Constant(collectionName)); 79 | } 80 | 81 | private Expression CreateLazyCollectionConstructExpression(Type itemsType, Expression ctxExpr, Expression parentExpr, Expression collectionNameExpr) 82 | { 83 | var lazyCollectionType = typeof(LazyCollection<>).MakeGenericType(itemsType); 84 | var constructors = lazyCollectionType.GetTypeInfo().DeclaredConstructors; 85 | var constructor = constructors 86 | .Select(c => new { Constructor = c, Parameters = c.GetParameters() }) 87 | .Where(x => x.Parameters.Length == 3 88 | && x.Parameters[0].ParameterType == typeof(DbContext) 89 | && x.Parameters[1].ParameterType == typeof(object) 90 | && x.Parameters[2].ParameterType == typeof(string)) 91 | .Select(x => x.Constructor) 92 | .FirstOrDefault(); 93 | 94 | Debug.Assert(constructor != null, "Valid constructor for LazyCollection was not found."); 95 | 96 | return Expression.New(constructor, ctxExpr, parentExpr, collectionNameExpr); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/Microsoft.EntityFrameworkCore.LazyLoading.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.3 5 | 1.1.1-beta4 6 | darxis 7 | 8 | This package enables LazyLoading for EntityFramework Core 9 | Entity Framework Core EF EntityFramework LazyLoading Lazy Include DbContext EntityFrameworkCore O/RM ORM Data 10 | https://github.com/darxis/EntityFramework.LazyLoading 11 | https://github.com/darxis/EntityFramework.LazyLoading 12 | 1.1.1.3 13 | 1.1.1.3 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Microsoft.EntityFrameworkCore.LazyLoading/Query/Internal/PerDbContextCompiledQueryCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Internal; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using Microsoft.EntityFrameworkCore.Query.Internal; 6 | 7 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Query.Internal 8 | { 9 | public class PerDbContextCompiledQueryCache : CompiledQueryCache 10 | { 11 | private readonly ICurrentDbContext _currentDbContext; 12 | 13 | public PerDbContextCompiledQueryCache(IDbContextServices contextServices) 14 | : base(contextServices) 15 | { 16 | _currentDbContext = contextServices.CurrentContext; 17 | } 18 | 19 | public override Func> GetOrAddAsyncQuery(object cacheKey, Func>> compiler) 20 | { 21 | var cacheKeyWithDbContextId = CreateCacheKeyWithDbContextId(cacheKey); 22 | return base.GetOrAddAsyncQuery(cacheKeyWithDbContextId, compiler); 23 | } 24 | 25 | public override Func GetOrAddQuery(object cacheKey, Func> compiler) 26 | { 27 | var cacheKeyWithDbContextId = CreateCacheKeyWithDbContextId(cacheKey); 28 | return base.GetOrAddQuery(cacheKeyWithDbContextId, compiler); 29 | } 30 | 31 | private object CreateCacheKeyWithDbContextId(object cacheKey) 32 | { 33 | return _currentDbContext.Context.GetHashCode() + "__DbContextCompiledQuery__" + cacheKey.GetHashCode(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Configuration/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Configuration 6 | { 7 | public sealed class Config 8 | { 9 | public enum Database 10 | { 11 | SqlServer, 12 | MySql, 13 | PostgreSql 14 | } 15 | 16 | public Database DatabaseType { get; } 17 | 18 | public DatabaseConfig SqlServerDatabaseConfig { get; } 19 | 20 | public DatabaseConfig MySqlDatabaseConfig { get; } 21 | 22 | public DatabaseConfig PostgreSqlDatabaseConfig { get; } 23 | 24 | private Config() 25 | { 26 | var config = new ConfigurationBuilder() 27 | .SetBasePath(Directory.GetCurrentDirectory()) 28 | .AddJsonFile("appsettings.json") 29 | .AddEnvironmentVariables("Microsoft_EntityFrameworkCore_LazyLoading_Tests_") 30 | .Build(); 31 | 32 | SqlServerDatabaseConfig = new DatabaseConfig(config["SqlServer:MainConnectionString"], config["SqlServer:SecondConnectionString"]); 33 | MySqlDatabaseConfig = new DatabaseConfig(config["MySql:MainConnectionString"], config["MySql:SecondConnectionString"]); 34 | PostgreSqlDatabaseConfig = new DatabaseConfig(config["PostgreSql:MainConnectionString"], config["PostgreSql:SecondConnectionString"]); 35 | 36 | if (!Enum.TryParse(config["DatabaseType"], true, out Database parsedDatabase)) 37 | { 38 | throw new Exception($"Invalid database type (was '{config["DatabaseType"]}')"); 39 | } 40 | 41 | DatabaseType = parsedDatabase; 42 | } 43 | 44 | private static Config _instance; 45 | 46 | public static Config GetInstance() 47 | { 48 | return _instance ?? (_instance = new Config()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Configuration/DatabaseConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Configuration 4 | { 5 | public enum ConnectionStringSelector 6 | { 7 | Main, 8 | Second 9 | } 10 | 11 | public class DatabaseConfig 12 | { 13 | public string MainConnectionString { get; } 14 | 15 | public string SecondConnectionString { get; } 16 | 17 | public DatabaseConfig(string mainConnectionString, string secondConnectionString) 18 | { 19 | MainConnectionString = mainConnectionString; 20 | SecondConnectionString = secondConnectionString; 21 | } 22 | 23 | public string GetConnectionString(ConnectionStringSelector connectionStringSelector) 24 | { 25 | switch (connectionStringSelector) 26 | { 27 | case ConnectionStringSelector.Main: 28 | return MainConnectionString; 29 | case ConnectionStringSelector.Second: 30 | return SecondConnectionString; 31 | default: 32 | throw new Exception($"Unknown {typeof(ConnectionStringSelector)} value (was {connectionStringSelector})."); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Data/DbInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Data 6 | { 7 | public static class DbInitializer 8 | { 9 | public static void Initialize(SchoolContext context) 10 | { 11 | context.Database.EnsureCreated(); 12 | 13 | // Look for any students. 14 | if (context.Students.Any()) 15 | { 16 | return; // DB has been seeded 17 | } 18 | 19 | var students = new [] 20 | { 21 | new Student { FirstMidName = "Carson", LastName = "Alexander", 22 | EnrollmentDate = DateTime.Parse("2010-09-01") }, 23 | new Student { FirstMidName = "Meredith", LastName = "Alonso", 24 | EnrollmentDate = DateTime.Parse("2012-09-01") }, 25 | new Student { FirstMidName = "Arturo", LastName = "Anand", 26 | EnrollmentDate = DateTime.Parse("2013-09-01") }, 27 | new Student { FirstMidName = "Gytis", LastName = "Barzdukas", 28 | EnrollmentDate = DateTime.Parse("2012-09-01") }, 29 | new Student { FirstMidName = "Yan", LastName = "Li", 30 | EnrollmentDate = DateTime.Parse("2012-09-01") }, 31 | new Student { FirstMidName = "Peggy", LastName = "Justice", 32 | EnrollmentDate = DateTime.Parse("2011-09-01") }, 33 | new Student { FirstMidName = "Laura", LastName = "Norman", 34 | EnrollmentDate = DateTime.Parse("2013-09-01") }, 35 | new Student { FirstMidName = "Nino", LastName = "Olivetto", 36 | EnrollmentDate = DateTime.Parse("2005-09-01") } 37 | }; 38 | 39 | foreach (Student s in students) 40 | { 41 | context.Students.Add(s); 42 | } 43 | context.SaveChanges(); 44 | 45 | var instructors = new[] 46 | { 47 | new Instructor { FirstMidName = "Kim", LastName = "Abercrombie", 48 | HireDate = DateTime.Parse("1995-03-11") }, 49 | new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri", 50 | HireDate = DateTime.Parse("2002-07-06") }, 51 | new Instructor { FirstMidName = "Roger", LastName = "Harui", 52 | HireDate = DateTime.Parse("1998-07-01") }, 53 | new Instructor { FirstMidName = "Candace", LastName = "Kapoor", 54 | HireDate = DateTime.Parse("2001-01-15") }, 55 | new Instructor { FirstMidName = "Roger", LastName = "Zheng", 56 | HireDate = DateTime.Parse("2004-02-12") } 57 | }; 58 | 59 | foreach (Instructor i in instructors) 60 | { 61 | context.Instructors.Add(i); 62 | } 63 | context.SaveChanges(); 64 | 65 | var departments = new[] 66 | { 67 | new Department { Name = "English", Budget = 350000, 68 | StartDate = DateTime.Parse("2007-09-01"), 69 | InstructorId = instructors.Single( i => i.LastName == "Abercrombie").Id }, 70 | new Department { Name = "Mathematics", Budget = 100000, 71 | StartDate = DateTime.Parse("2007-09-01"), 72 | InstructorId = instructors.Single( i => i.LastName == "Fakhouri").Id }, 73 | new Department { Name = "Engineering", Budget = 350000, 74 | StartDate = DateTime.Parse("2007-09-01"), 75 | InstructorId = instructors.Single( i => i.LastName == "Harui").Id }, 76 | new Department { Name = "Economics", Budget = 100000, 77 | StartDate = DateTime.Parse("2007-09-01"), 78 | InstructorId = instructors.Single( i => i.LastName == "Kapoor").Id } 79 | }; 80 | 81 | foreach (Department d in departments) 82 | { 83 | context.Departments.Add(d); 84 | } 85 | context.SaveChanges(); 86 | 87 | var courses = new[] 88 | { 89 | new Course {CourseId = 1050, Title = "Chemistry", Credits = 3, 90 | DepartmentId = departments.Single( s => s.Name == "Engineering").DepartmentId 91 | }, 92 | new Course {CourseId = 4022, Title = "Microeconomics", Credits = 3, 93 | DepartmentId = departments.Single( s => s.Name == "Economics").DepartmentId 94 | }, 95 | new Course {CourseId = 4041, Title = "Macroeconomics", Credits = 3, 96 | DepartmentId = departments.Single( s => s.Name == "Economics").DepartmentId 97 | }, 98 | new Course {CourseId = 1045, Title = "Calculus", Credits = 4, 99 | DepartmentId = departments.Single( s => s.Name == "Mathematics").DepartmentId 100 | }, 101 | new Course {CourseId = 3141, Title = "Trigonometry", Credits = 4, 102 | DepartmentId = departments.Single( s => s.Name == "Mathematics").DepartmentId 103 | }, 104 | new Course {CourseId = 2021, Title = "Composition", Credits = 3, 105 | DepartmentId = departments.Single( s => s.Name == "English").DepartmentId 106 | }, 107 | new Course {CourseId = 2042, Title = "Literature", Credits = 4, 108 | DepartmentId = departments.Single( s => s.Name == "English").DepartmentId 109 | }, 110 | }; 111 | 112 | foreach (Course c in courses) 113 | { 114 | context.Courses.Add(c); 115 | } 116 | context.SaveChanges(); 117 | 118 | var officeAssignments = new[] 119 | { 120 | new OfficeAssignment { 121 | InstructorId = instructors.Single( i => i.LastName == "Fakhouri").Id, 122 | Location = "Smith 17" }, 123 | new OfficeAssignment { 124 | InstructorId = instructors.Single( i => i.LastName == "Harui").Id, 125 | Location = "Gowan 27" }, 126 | new OfficeAssignment { 127 | InstructorId = instructors.Single( i => i.LastName == "Kapoor").Id, 128 | Location = "Thompson 304" }, 129 | }; 130 | 131 | foreach (OfficeAssignment o in officeAssignments) 132 | { 133 | context.OfficeAssignments.Add(o); 134 | } 135 | context.SaveChanges(); 136 | 137 | var courseInstructors = new[] 138 | { 139 | new CourseAssignment { 140 | CourseId = courses.Single(c => c.Title == "Chemistry" ).CourseId, 141 | InstructorId = instructors.Single(i => i.LastName == "Kapoor").Id 142 | }, 143 | new CourseAssignment { 144 | CourseId = courses.Single(c => c.Title == "Chemistry" ).CourseId, 145 | InstructorId = instructors.Single(i => i.LastName == "Harui").Id 146 | }, 147 | new CourseAssignment { 148 | CourseId = courses.Single(c => c.Title == "Microeconomics" ).CourseId, 149 | InstructorId = instructors.Single(i => i.LastName == "Zheng").Id 150 | }, 151 | new CourseAssignment { 152 | CourseId = courses.Single(c => c.Title == "Macroeconomics" ).CourseId, 153 | InstructorId = instructors.Single(i => i.LastName == "Zheng").Id 154 | }, 155 | new CourseAssignment { 156 | CourseId = courses.Single(c => c.Title == "Calculus" ).CourseId, 157 | InstructorId = instructors.Single(i => i.LastName == "Fakhouri").Id 158 | }, 159 | new CourseAssignment { 160 | CourseId = courses.Single(c => c.Title == "Trigonometry" ).CourseId, 161 | InstructorId = instructors.Single(i => i.LastName == "Harui").Id 162 | }, 163 | new CourseAssignment { 164 | CourseId = courses.Single(c => c.Title == "Composition" ).CourseId, 165 | InstructorId = instructors.Single(i => i.LastName == "Abercrombie").Id 166 | }, 167 | new CourseAssignment { 168 | CourseId = courses.Single(c => c.Title == "Literature" ).CourseId, 169 | InstructorId = instructors.Single(i => i.LastName == "Abercrombie").Id 170 | }, 171 | }; 172 | 173 | foreach (CourseAssignment ci in courseInstructors) 174 | { 175 | context.CourseAssignments.Add(ci); 176 | } 177 | context.SaveChanges(); 178 | 179 | var enrollments = new[] 180 | { 181 | new Enrollment { 182 | StudentId = students.Single(s => s.LastName == "Alexander").Id, 183 | CourseId = courses.Single(c => c.Title == "Chemistry" ).CourseId, 184 | Grade = Grade.A 185 | }, 186 | new Enrollment { 187 | StudentId = students.Single(s => s.LastName == "Alexander").Id, 188 | CourseId = courses.Single(c => c.Title == "Microeconomics" ).CourseId, 189 | Grade = Grade.C 190 | }, 191 | new Enrollment { 192 | StudentId = students.Single(s => s.LastName == "Alexander").Id, 193 | CourseId = courses.Single(c => c.Title == "Macroeconomics" ).CourseId, 194 | Grade = Grade.B 195 | }, 196 | new Enrollment { 197 | StudentId = students.Single(s => s.LastName == "Alonso").Id, 198 | CourseId = courses.Single(c => c.Title == "Calculus" ).CourseId, 199 | Grade = Grade.B 200 | }, 201 | new Enrollment { 202 | StudentId = students.Single(s => s.LastName == "Alonso").Id, 203 | CourseId = courses.Single(c => c.Title == "Trigonometry" ).CourseId, 204 | Grade = Grade.B 205 | }, 206 | new Enrollment { 207 | StudentId = students.Single(s => s.LastName == "Alonso").Id, 208 | CourseId = courses.Single(c => c.Title == "Composition" ).CourseId, 209 | Grade = Grade.B 210 | }, 211 | new Enrollment { 212 | StudentId = students.Single(s => s.LastName == "Anand").Id, 213 | CourseId = courses.Single(c => c.Title == "Chemistry" ).CourseId 214 | }, 215 | new Enrollment { 216 | StudentId = students.Single(s => s.LastName == "Anand").Id, 217 | CourseId = courses.Single(c => c.Title == "Microeconomics").CourseId, 218 | Grade = Grade.B 219 | }, 220 | new Enrollment { 221 | StudentId = students.Single(s => s.LastName == "Barzdukas").Id, 222 | CourseId = courses.Single(c => c.Title == "Chemistry").CourseId, 223 | Grade = Grade.B 224 | }, 225 | new Enrollment { 226 | StudentId = students.Single(s => s.LastName == "Li").Id, 227 | CourseId = courses.Single(c => c.Title == "Composition").CourseId, 228 | Grade = Grade.B 229 | }, 230 | new Enrollment { 231 | StudentId = students.Single(s => s.LastName == "Justice").Id, 232 | CourseId = courses.Single(c => c.Title == "Literature").CourseId, 233 | Grade = Grade.B 234 | } 235 | }; 236 | 237 | foreach (Enrollment e in enrollments) 238 | { 239 | var enrollmentInDataBase = context.Enrollments.SingleOrDefault(s => s.Student.Id == e.StudentId && 240 | s.Course.CourseId == e.CourseId); 241 | if (enrollmentInDataBase == null) 242 | { 243 | context.Enrollments.Add(e); 244 | } 245 | } 246 | context.SaveChanges(); 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Data/SchoolContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Data 4 | { 5 | public class SchoolContext : DbContext 6 | { 7 | public SchoolContext(DbContextOptions options) : base(options) 8 | { 9 | } 10 | 11 | public DbSet Courses { get; set; } 12 | public DbSet Enrollments { get; set; } 13 | public DbSet Students { get; set; } 14 | public DbSet Departments { get; set; } 15 | public DbSet Instructors { get; set; } 16 | public DbSet OfficeAssignments { get; set; } 17 | public DbSet CourseAssignments { get; set; } 18 | public DbSet People { get; set; } 19 | 20 | protected override void OnModelCreating(ModelBuilder modelBuilder) 21 | { 22 | modelBuilder.Entity(); 23 | modelBuilder.Entity(); 24 | modelBuilder.Entity(); 25 | modelBuilder.Entity(); 26 | modelBuilder.Entity(); 27 | modelBuilder.Entity(); 28 | modelBuilder.Entity(); 29 | modelBuilder.Entity() 30 | .HasDiscriminator("Discriminator") 31 | .HasValue(nameof(Student)) 32 | .HasValue(nameof(Instructor)); 33 | 34 | modelBuilder.Entity() 35 | .HasKey(c => new { CourseID = c.CourseId, InstructorID = c.InstructorId }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Data/SchoolContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using Microsoft.EntityFrameworkCore.LazyLoading.Tests.Configuration; 4 | using MySQL.Data.EntityFrameworkCore.Extensions; 5 | 6 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Data 7 | { 8 | public class SchoolContextFactory : IDbContextFactory 9 | { 10 | public SchoolContext Create(DbContextFactoryOptions options) 11 | { 12 | return Create(options, ConnectionStringSelector.Main); 13 | } 14 | 15 | public SchoolContext Create(DbContextFactoryOptions options, ConnectionStringSelector connectionStringSelector) 16 | { 17 | var config = Config.GetInstance(); 18 | 19 | var dbContextOptionsBuilder = new DbContextOptionsBuilder(); 20 | 21 | switch (config.DatabaseType) 22 | { 23 | case Config.Database.MySql: 24 | dbContextOptionsBuilder 25 | .UseMySQL(config.MySqlDatabaseConfig.GetConnectionString(connectionStringSelector)); 26 | break; 27 | case Config.Database.SqlServer: 28 | dbContextOptionsBuilder 29 | .UseSqlServer(config.SqlServerDatabaseConfig.GetConnectionString(connectionStringSelector)); 30 | break; 31 | case Config.Database.PostgreSql: 32 | dbContextOptionsBuilder 33 | .UseNpgsql(config.PostgreSqlDatabaseConfig.GetConnectionString(connectionStringSelector)); 34 | break; 35 | default: 36 | throw new Exception($"Unknown database type (was '{config.DatabaseType}')"); 37 | } 38 | 39 | dbContextOptionsBuilder.UseLazyLoading(); 40 | 41 | var ctx = new SchoolContext(dbContextOptionsBuilder.Options); 42 | 43 | return ctx; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/DatabaseTestBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Infrastructure; 2 | using Microsoft.EntityFrameworkCore.LazyLoading.Tests.Configuration; 3 | using Microsoft.EntityFrameworkCore.LazyLoading.Tests.Data; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests 6 | { 7 | public abstract class DatabaseTestBase 8 | { 9 | protected enum ContextInitializationOptions 10 | { 11 | SeedSampleData, 12 | CleanupData, 13 | DoNothing 14 | } 15 | 16 | private readonly SchoolContextFactory _ctxFactory = new SchoolContextFactory(); 17 | 18 | protected SchoolContext CreateDbContext(ConnectionStringSelector connectionStringSelector, ContextInitializationOptions options) 19 | { 20 | SchoolContext ctx; 21 | if (options == ContextInitializationOptions.CleanupData || 22 | options == ContextInitializationOptions.SeedSampleData) 23 | { 24 | using (ctx = _ctxFactory.Create(new DbContextFactoryOptions(), connectionStringSelector)) 25 | { 26 | ctx.Database.EnsureDeleted(); 27 | if (options == ContextInitializationOptions.SeedSampleData) 28 | { 29 | DbInitializer.Initialize(ctx); 30 | } 31 | } 32 | } 33 | 34 | ctx = _ctxFactory.Create(new DbContextFactoryOptions(), connectionStringSelector); 35 | ctx.Database.EnsureCreated(); 36 | ctx.Database.Migrate(); 37 | 38 | return ctx; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Models/Course.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models 6 | { 7 | public class Course 8 | { 9 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 10 | [Display(Name = "Number")] 11 | public int CourseId { get; set; } 12 | 13 | [StringLength(50, MinimumLength = 3)] 14 | public string Title { get; set; } 15 | 16 | [Range(0, 5)] 17 | public int Credits { get; set; } 18 | 19 | public int DepartmentId { get; set; } 20 | 21 | private readonly LazyReference _departmentLazy = new LazyReference(); 22 | public Department Department 23 | { 24 | get => _departmentLazy.GetValue(this); 25 | set => _departmentLazy.SetValue(value); 26 | } 27 | 28 | public ICollection Enrollments { get; set; } 29 | 30 | public ICollection CourseAssignments { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Models/CourseAssignment.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models 2 | { 3 | public class CourseAssignment 4 | { 5 | public int InstructorId { get; set; } 6 | public int CourseId { get; set; } 7 | private readonly LazyReference _instructorLazy = new LazyReference(); 8 | public Instructor Instructor 9 | { 10 | get => _instructorLazy.GetValue(this); 11 | set => _instructorLazy.SetValue(value); 12 | } 13 | private readonly LazyReference _courseLazy = new LazyReference(); 14 | public Course Course 15 | { 16 | get => _courseLazy.GetValue(this); 17 | set => _courseLazy.SetValue(value); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Models/Department.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models 7 | { 8 | public class Department 9 | { 10 | public int DepartmentId { get; set; } 11 | 12 | [StringLength(50, MinimumLength = 3)] 13 | public string Name { get; set; } 14 | 15 | [DataType(DataType.Currency)] 16 | [Column(TypeName = "money")] 17 | public decimal Budget { get; set; } 18 | 19 | [DataType(DataType.Date)] 20 | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] 21 | [Display(Name = "Start Date")] 22 | public DateTime StartDate { get; set; } 23 | 24 | public int? InstructorId { get; set; } 25 | 26 | [Timestamp] 27 | public byte[] RowVersion { get; set; } 28 | private readonly LazyReference _administratorLazy = new LazyReference(); 29 | 30 | public Instructor Administrator 31 | { 32 | get => _administratorLazy.GetValue(this); 33 | set => _administratorLazy.SetValue(value); 34 | } 35 | public ICollection Courses { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Models/Enrollment.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models 4 | { 5 | public enum Grade 6 | { 7 | A, B, C, D, F 8 | } 9 | 10 | public class Enrollment 11 | { 12 | public int EnrollmentId { get; set; } 13 | public int CourseId { get; set; } 14 | public int StudentId { get; set; } 15 | [DisplayFormat(NullDisplayText = "No grade")] 16 | public Grade? Grade { get; set; } 17 | 18 | private readonly LazyReference _courseLazy = new LazyReference(); 19 | public Course Course 20 | { 21 | get => _courseLazy.GetValue(this); 22 | set => _courseLazy.SetValue(value); 23 | } 24 | private readonly LazyReference _studentLazy = new LazyReference(); 25 | public Student Student 26 | { 27 | get => _studentLazy.GetValue(this); 28 | set => _studentLazy.SetValue(value); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Models/Instructor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models 6 | { 7 | public class Instructor : Person 8 | { 9 | [DataType(DataType.Date)] 10 | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] 11 | [Display(Name = "Hire Date")] 12 | public DateTime HireDate { get; set; } 13 | 14 | public ICollection CourseAssignments { get; set; } 15 | private readonly LazyReference _officeAssignmentLazy = new LazyReference(); 16 | public OfficeAssignment OfficeAssignment 17 | { 18 | get => _officeAssignmentLazy.GetValue(this); 19 | set => _officeAssignmentLazy.SetValue(value); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Models/OfficeAssignment.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models 4 | { 5 | public class OfficeAssignment 6 | { 7 | [Key] 8 | public int InstructorId { get; set; } 9 | [StringLength(50)] 10 | [Display(Name = "Office Location")] 11 | public string Location { get; set; } 12 | 13 | private readonly LazyReference _instructorLazy = new LazyReference(); 14 | public Instructor Instructor 15 | { 16 | get => _instructorLazy.GetValue(this); 17 | set => _instructorLazy.SetValue(value); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Models/Person.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models 5 | { 6 | public abstract class Person 7 | { 8 | public int Id { get; set; } 9 | 10 | [Required] 11 | [StringLength(50)] 12 | [Display(Name = "Last Name")] 13 | public string LastName { get; set; } 14 | [Required] 15 | [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")] 16 | [Column("FirstName")] 17 | [Display(Name = "First Name")] 18 | public string FirstMidName { get; set; } 19 | 20 | [Display(Name = "Full Name")] 21 | public string FullName => LastName + ", " + FirstMidName; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Models/Student.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models 6 | { 7 | public class Student : Person 8 | { 9 | [DataType(DataType.Date)] 10 | [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] 11 | [Display(Name = "Enrollment Date")] 12 | public DateTime EnrollmentDate { get; set; } 13 | 14 | public ICollection Enrollments { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.EntityFrameworkCore.LazyLoading.Tests.Configuration; 4 | using Microsoft.EntityFrameworkCore.LazyLoading.Tests.Models; 5 | using Xunit; 6 | 7 | namespace Microsoft.EntityFrameworkCore.LazyLoading.Tests 8 | { 9 | public class Tests : DatabaseTestBase 10 | { 11 | [Fact] 12 | public void ShouldNotThrowWhenExecutingTheSameQueryOnTwoDifferentDbContextsAfterDisposingTheFirstOne() 13 | { 14 | for (var i = 0; i < 2; ++i) 15 | { 16 | using (var ctx = CreateDbContext(ConnectionStringSelector.Main, ContextInitializationOptions.SeedSampleData)) 17 | { 18 | var instructorAbercrombie = ctx.Set().First(x => x.LastName == "Abercrombie"); 19 | var coursesOfinstructorAbercrombie = instructorAbercrombie.CourseAssignments.Select(x => x.Course); 20 | 21 | Assert.NotEmpty(coursesOfinstructorAbercrombie); 22 | } 23 | } 24 | } 25 | 26 | [Fact] 27 | public void ShouldUseTheCorrectDbContextNotTheNewestOne() 28 | { 29 | using (var ctx = CreateDbContext(ConnectionStringSelector.Main, ContextInitializationOptions.CleanupData)) 30 | { 31 | var course1 = new Course { CourseId = 1, Title = "Course 1", Department = new Department() }; 32 | var course2 = new Course { CourseId = 2, Title = "Course 2", Department = new Department() }; 33 | var instructor = new Instructor 34 | { 35 | FirstMidName = "Entity", 36 | LastName = "Framework", 37 | OfficeAssignment = new OfficeAssignment 38 | { 39 | Location = "Washington" 40 | }, 41 | CourseAssignments = new List 42 | { 43 | new CourseAssignment 44 | { 45 | Course = course1 46 | }, 47 | new CourseAssignment 48 | { 49 | Course = course2 50 | } 51 | } 52 | }; 53 | 54 | ctx.Instructors.Add(instructor); 55 | ctx.SaveChanges(); 56 | } 57 | 58 | using (var ctx = CreateDbContext(ConnectionStringSelector.Second, ContextInitializationOptions.CleanupData)) 59 | { 60 | var instructor = new Instructor 61 | { 62 | FirstMidName = "Entity", 63 | LastName = "Framework", 64 | OfficeAssignment = new OfficeAssignment 65 | { 66 | Location = "New York" 67 | }, 68 | CourseAssignments = new List() 69 | }; 70 | 71 | ctx.Instructors.Add(instructor); 72 | ctx.SaveChanges(); 73 | } 74 | 75 | using (var ctx = CreateDbContext(ConnectionStringSelector.Main, ContextInitializationOptions.DoNothing)) 76 | { 77 | using (var newCtx = CreateDbContext(ConnectionStringSelector.Second, ContextInitializationOptions.DoNothing)) 78 | { 79 | var newInstructor = newCtx.Instructors.FirstOrDefault(); 80 | Assert.NotNull(newInstructor); 81 | Assert.Equal("New York", newInstructor.OfficeAssignment.Location); 82 | 83 | var instructor = ctx.Instructors.FirstOrDefault(); 84 | Assert.NotNull(instructor); 85 | 86 | Assert.Equal("Washington", instructor.OfficeAssignment.Location); 87 | Assert.Equal(2, instructor.CourseAssignments.Count); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/Microsoft.EntityFrameworkCore.LazyLoading.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "DatabaseType": "SQLServer", 3 | "SqlServer": { 4 | "MainConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity;Trusted_Connection=True;MultipleActiveResultSets=true", 5 | "SecondConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity2;Trusted_Connection=True;MultipleActiveResultSets=true" 6 | }, 7 | "MySql": { 8 | "MainConnectionString": "server=localhost;userid=root;password=;Database=ContosoUniversity;", 9 | "SecondConnectionString": "server=localhost;userid=root;password=;Database=ContosoUniversity2;" 10 | }, 11 | "PostgreSql": { 12 | "MainConnectionString": "User ID=postgres;Password=;Host=localhost;Port=5432;Database=ContosoUniversity;Pooling=true;", 13 | "SecondConnectionString": "User ID=postgres;Password=;Host=localhost;Port=5432;Database=ContosoUniversity2;Pooling=true;" 14 | } 15 | } --------------------------------------------------------------------------------