diff --git a/.gitignore b/.gitignore
index dd0049e..4aa3348 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,19 @@
+
+# Created by https://www.gitignore.io/api/aspnetcore,visualstudio
+
+### ASPNETCore ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
+*.userosscache
*.sln.docstates
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
# Build results
[Dd]ebug/
[Dd]ebugPublic/
@@ -13,19 +21,21 @@
[Rr]eleases/
x64/
x86/
-build/
bld/
[Bb]in/
[Oo]bj/
+[Ll]og/
-# Roslyn cache directories
-*.ide/
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
-#NUNIT
+# NUNIT
*.VisualState.xml
TestResult.xml
@@ -34,6 +44,11 @@ TestResult.xml
[Rr]eleasePS/
dlldata.c
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
*_i.c
*_p.c
*_i.h
@@ -66,14 +81,18 @@ _Chutzpah*
ipch/
*.aps
*.ncb
+*.opendb
*.opensdf
*.sdf
*.cachefile
+*.VC.db
+*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
+*.sap
# TFS 2012 Local Workspace
$tf/
@@ -86,7 +105,7 @@ _ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
-# JustCode is a .NET coding addin-in
+# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
@@ -95,9 +114,14 @@ _TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
# NCrunch
_NCrunch_*
.*crunch*.local.xml
+nCrunchTemp_*
# MightyMoose
*.mm.*
@@ -125,39 +149,63 @@ publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
-# TODO: Comment the next line if you want to checkin your web deploy settings
+# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
-# If using the old MSBuild-Integrated Package Restore, uncomment this:
+# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
-# Windows Azure Build Output
+# Microsoft Azure Build Output
csx/
*.build.csdef
-# Windows Store app package directory
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
# Others
-sql/
-*.Cache
ClientBin/
-[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
+*.jfm
*.pfx
*.publishsettings
node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
# RIA/Silverlight projects
Generated_Code/
@@ -182,4 +230,194 @@ UpgradeLog*.htm
# Microsoft Fakes
FakesAssemblies/
-/.vs/DbContextScope/v15/sqlite3
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/
+
+### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+
+# Build results
+
+# Visual Studio 2015 cache/options directory
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+
+# NUNIT
+
+# Build Results of an ATL Project
+
+# .NET Core
+**/Properties/launchSettings.json
+
+
+# Chutzpah Test files
+
+# Visual C++ cache files
+
+# Visual Studio profiler
+
+# TFS 2012 Local Workspace
+
+# Guidance Automation Toolkit
+
+# ReSharper is a .NET coding add-in
+
+# JustCode is a .NET coding add-in
+
+# TeamCity is a build add-in
+
+# DotCover is a Code Coverage Tool
+
+# Visual Studio code coverage results
+
+# NCrunch
+
+# MightyMoose
+
+# Web workbench (sass)
+
+# Installshield output folder
+
+# DocProject is a documentation generator add-in
+
+# Click-Once directory
+
+# Publish Web Output
+# TODO: Uncomment the next line to ignore your web deploy settings.
+# By default, sensitive information, such as encrypted password
+# should be stored in the .pubxml.user file.
+#*.pubxml
+*.pubxml.user
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+
+# NuGet Packages
+# The packages folder can be ignored because of Package Restore
+# except build/, which is used as an MSBuild target.
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+
+# Microsoft Azure Build Output
+
+# Microsoft Azure Emulator
+
+# Windows Store app package directories and files
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+# but keep track of directories ending in .cache
+
+# Others
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+
+# SQL Server files
+*.ndf
+
+# Business Intelligence projects
+
+# Microsoft Fakes
+
+# GhostDoc plugin setting file
+
+# Node.js Tools for Visual Studio
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+
+# Visual Studio 6 workspace options file
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+
+# Paket dependency manager
+
+# FAKE - F# Make
+
+# JetBrains Rider
+
+# CodeRush
+
+# Python Tools for Visual Studio (PTVS)
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+### VisualStudio Patch ###
+# By default, sensitive information, such as encrypted password
+# should be stored in the .pubxml.user file.
+
+
+# End of https://www.gitignore.io/api/aspnetcore,visualstudio
diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config
deleted file mode 100644
index 67f8ea0..0000000
--- a/.nuget/NuGet.Config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe
deleted file mode 100644
index 8dd7e45..0000000
Binary files a/.nuget/NuGet.exe and /dev/null differ
diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets
deleted file mode 100644
index 3f8c37b..0000000
--- a/.nuget/NuGet.targets
+++ /dev/null
@@ -1,144 +0,0 @@
-
-
-
- $(MSBuildProjectDirectory)\..\
-
-
- false
-
-
- false
-
-
- true
-
-
- false
-
-
-
-
-
-
-
-
-
-
- $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
-
-
-
-
- $(SolutionDir).nuget
-
-
-
- $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config
- $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config
-
-
-
- $(MSBuildProjectDirectory)\packages.config
- $(PackagesProjectConfig)
-
-
-
-
- $(NuGetToolsPath)\NuGet.exe
- @(PackageSource)
-
- "$(NuGetExePath)"
- mono --runtime=v4.0.30319 "$(NuGetExePath)"
-
- $(TargetDir.Trim('\\'))
-
- -RequireConsent
- -NonInteractive
-
- "$(SolutionDir) "
- "$(SolutionDir)"
-
-
- $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
- $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
-
-
-
- RestorePackages;
- $(BuildDependsOn);
-
-
-
-
- $(BuildDependsOn);
- BuildPackage;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/DbContextScope/DbContextScope.csproj b/DbContextScope/DbContextScope.csproj
deleted file mode 100644
index e87ad91..0000000
--- a/DbContextScope/DbContextScope.csproj
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
- net461
-
- EntityFrameworkCore.DbContextScope
-
- 1.0.0
- 1.0.0
-
-
-
- Mehdi El Gueddari,Tim Calvert
- EntityFrameworkCore.DbContextScope
- https://github.com/tncalvert/DbContextScope
- https://raw.githubusercontent.com/tncalvert/DbContextScope/master/LICENSE
- 1.0.0
- DbContextScope for EF Core. Forked from https://github.com/mehdime/DbContextScope.
- git
- https://github.com/tncalvert/DbContextScope
- EntityFramework EFCore DbContext DbContextScope
-
-
-
-
-
-
-
diff --git a/DbContextScope/DbContextScope.licenseheader b/DbContextScope/DbContextScope.licenseheader
deleted file mode 100644
index 2a2a49b..0000000
--- a/DbContextScope/DbContextScope.licenseheader
+++ /dev/null
@@ -1,9 +0,0 @@
-extensions: designer.cs generated.cs
-extensions: .cs .cpp .h
-/*
- * Copyright (C) 2014 Mehdi El Gueddari
- * http://mehdi.me
- *
- * This software may be modified and distributed under the terms
- * of the MIT license. See the LICENSE file for details.
- */
diff --git a/Demo/Demo.csproj b/Demo/Demo.csproj
deleted file mode 100644
index fb56285..0000000
--- a/Demo/Demo.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- Exe
- net461
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Demo/Program.cs b/Demo/Program.cs
deleted file mode 100644
index 9512646..0000000
--- a/Demo/Program.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-using EntityFrameworkCore.DbContextScope;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Numero3.EntityFramework.Demo.BusinessLogicServices;
-using Numero3.EntityFramework.Demo.CommandModel;
-using Numero3.EntityFramework.Demo.DatabaseContext;
-using Numero3.EntityFramework.Demo.Repositories;
-using System;
-using System.Linq;
-
-namespace Numero3.EntityFramework.Demo {
- class Program {
- static void Main(string[] args) {
- //-- Poor-man DI - build our dependencies by hand for this demo
- var dbContextScopeFactory = new DbContextScopeFactory(new DbContextFactory());
- var ambientDbContextLocator = new AmbientDbContextLocator();
- var userRepository = new UserRepository(ambientDbContextLocator);
-
- var userCreationService = new UserCreationService(dbContextScopeFactory, userRepository);
- var userQueryService = new UserQueryService(dbContextScopeFactory, userRepository);
- var userEmailService = new UserEmailService(dbContextScopeFactory);
- var userCreditScoreService = new UserCreditScoreService(dbContextScopeFactory);
-
- try {
- Console.WriteLine("This demo uses an EF Core In Memory database. It does not create any external databases.");
- Console.WriteLine("");
-
- //-- Demo of typical usage for read and writes
- Console.WriteLine("Creating a user called Mary...");
- var marysSpec = new UserCreationSpec("Mary", "mary@example.com");
- userCreationService.CreateUser(marysSpec);
- Console.WriteLine("Done.\n");
-
- Console.WriteLine("Trying to retrieve our newly created user from the data store...");
- var mary = userQueryService.GetUser(marysSpec.Id);
- Console.WriteLine("OK. Persisted user: {0}", mary);
-
- Console.WriteLine("Press enter to continue...");
- Console.ReadLine();
-
- //-- Demo of nested DbContextScopes
- Console.WriteLine("Creating 2 new users called John and Jeanne in an atomic transaction...");
- var johnSpec = new UserCreationSpec("John", "john@example.com");
- var jeanneSpec = new UserCreationSpec("Jeanne", "jeanne@example.com");
- userCreationService.CreateListOfUsers(johnSpec, jeanneSpec);
- Console.WriteLine("Done.\n");
-
- Console.WriteLine("Trying to retrieve our newly created users from the data store...");
- var createdUsers = userQueryService.GetUsers(johnSpec.Id, jeanneSpec.Id);
- Console.WriteLine("OK. Found {0} persisted users.", createdUsers.Count());
-
- Console.WriteLine("Press enter to continue...");
- Console.ReadLine();
-
- //-- Demo of nested DbContextScopes in the face of an exception.
- // If any of the provided users failed to get persisted, none should get persisted.
- Console.WriteLine("Creating 2 new users called Julie and Marc in an atomic transaction. Will make the persistence of the second user fail intentionally in order to test the atomicity of the transaction...");
- var julieSpec = new UserCreationSpec("Julie", "julie@example.com");
- var marcSpec = new UserCreationSpec("Marc", "marc@example.com");
- try {
- userCreationService.CreateListOfUsersWithIntentionalFailure(julieSpec, marcSpec);
- Console.WriteLine("Done.\n");
- } catch (Exception e) {
- Console.WriteLine(e.Message);
- Console.WriteLine();
- }
-
- Console.WriteLine("Trying to retrieve our newly created users from the data store...");
- var maybeCreatedUsers = userQueryService.GetUsers(julieSpec.Id, marcSpec.Id);
- Console.WriteLine("Found {0} persisted users. If this number is 0, we're all good. If this number is not 0, we have a big problem.", maybeCreatedUsers.Count());
-
- Console.WriteLine("Press enter to continue...");
- Console.ReadLine();
-
- //-- Demo of DbContextScope within an async flow
- Console.WriteLine("Trying to retrieve two users John and Jeanne sequentially in an asynchronous manner...");
- // We're going to block on the async task here as we don't have a choice. No risk of deadlocking in any case as console apps
- // don't have a synchronization context.
- var usersFoundAsync = userQueryService.GetTwoUsersAsync(johnSpec.Id, jeanneSpec.Id).Result;
- Console.WriteLine("OK. Found {0} persisted users.", usersFoundAsync.Count());
-
- Console.WriteLine("Press enter to continue...");
- Console.ReadLine();
-
- //-- Demo of explicit database transaction.
- Console.WriteLine("Trying to retrieve user John within a READ UNCOMMITTED database transaction...");
- // You'll want to use SQL Profiler or Entity Framework Profiler to verify that the correct transaction isolation
- // level is being used.
- var userMaybeUncommitted = userQueryService.GetUserUncommitted(johnSpec.Id);
- Console.WriteLine("OK. User found: {0}", userMaybeUncommitted);
-
- Console.WriteLine("Press enter to continue...");
- Console.ReadLine();
-
- //-- Demo of disabling the DbContextScope nesting behaviour in order to force the persistence of changes made to entities
- // This is a pretty advanced feature that you can safely ignore until you actually need it.
- Console.WriteLine("Will simulate sending a Welcome email to John...");
-
- using (var parentScope = dbContextScopeFactory.Create()) {
- var parentDbContext = parentScope.DbContexts.Get();
-
- // Load John in the parent DbContext
- var john = parentDbContext.Users.Find(johnSpec.Id);
- Console.WriteLine("Before calling SendWelcomeEmail(), john.WelcomeEmailSent = " + john.WelcomeEmailSent);
-
- // Now call our SendWelcomeEmail() business logic service method, which will
- // update John in a non-nested child context
- userEmailService.SendWelcomeEmail(johnSpec.Id);
-
- // Verify that we can see the modifications made to John by the SendWelcomeEmail() method
- Console.WriteLine("After calling SendWelcomeEmail(), john.WelcomeEmailSent = " + john.WelcomeEmailSent);
-
- // Note that even though we're not calling SaveChanges() in the parent scope here, the changes
- // made to John by SendWelcomeEmail() will remain persisted in the database as SendWelcomeEmail()
- // forced the creation of a new DbContextScope.
- }
-
- Console.WriteLine("Press enter to continue...");
- Console.ReadLine();
-
- //-- Demonstration of DbContextScope and parallel programming
- Console.WriteLine("Calculating and storing the credit score of all users in the database in parallel...");
- userCreditScoreService.UpdateCreditScoreForAllUsers();
- Console.WriteLine("Done.");
- } catch (Exception e) {
- Console.WriteLine(e);
- }
-
- Console.WriteLine();
- Console.WriteLine("The end.");
- Console.WriteLine("Press enter to exit...");
- Console.ReadLine();
- }
- }
-
- class DbContextFactory : IDbContextFactory {
- public TDbContext CreateDbContext() where TDbContext : DbContext {
- if (typeof(TDbContext) == typeof(UserManagementDbContext)) {
- var config = new DbContextOptionsBuilder()
- .UseInMemoryDatabase()
- .ConfigureWarnings(warnings => {
- warnings.Ignore(InMemoryEventId.TransactionIgnoredWarning);
- });
- return new UserManagementDbContext(config.Options) as TDbContext;
- }
-
- throw new NotImplementedException(typeof(TDbContext).Name);
- }
- }
-}
diff --git a/Directory.build.props b/Directory.build.props
new file mode 100644
index 0000000..8f5af2b
--- /dev/null
+++ b/Directory.build.props
@@ -0,0 +1,35 @@
+
+
+ latest
+ Dominic Jonas
+ Dominic Jonas
+ DoJo.EntityFrameworkCore.DbContextScope
+ https://github.com/dojo90/DbContextScope
+ https://raw.githubusercontent.com/dojo90/DbContextScope/master/LICENSE
+ git@github.com:dojo90/DbContextScope.git
+ git
+ sentinel nlog
+ DbContextScope for EF v2|v3 Core. Forked from https://github.com/tncalvert/DbContextScope.
+ EntityFramework EFCore DbContext DbContextScope
+
+
+
+
+ false
+ false
+ false
+ false
+
+ $(GitVersion_FullSemVer)
+ $(GitVersion_MajorMinorPatch)
+ $(GitVersion_NuGetPreReleaseTag)
+ $(GitVersion_PreReleaseTag)
+ $(GitVersion_NuGetVersion)
+ $(GitVersion_FullSemVer)
+ $(GitVersion_InformationalVersion)
+ $(GitVersion_AssemblySemVer)
+ $(GitVersion_AssemblySemFileVer)
+ $(GitVersion_BranchName)
+ $(GitVersion_Sha)
+
+
\ No newline at end of file
diff --git a/GitVersion.yml b/GitVersion.yml
new file mode 100644
index 0000000..bc6c5ae
--- /dev/null
+++ b/GitVersion.yml
@@ -0,0 +1,19 @@
+mode: ContinuousDeployment
+branches:
+ issue:
+ tag: issue-{BranchName}
+ increment: Inherit
+ prevent-increment-of-merged-branch-version: false
+ regex: ^(?=\d+-)
+ source-branches:
+ - develop
+ - master
+ - release
+ - feature
+ - support
+ - hotfix
+ - issue
+ develop:
+ increment: Patch
+ignore:
+ sha: []
diff --git a/README.md b/README.md
index 26f6547..6cde05b 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,29 @@
-DbContextScope
-==============
+## Foreword
+This is a fork from [tncalvert/DbContextScope](https://github.com/tncalvert/DbContextScope). The target framework is `.NET Standart2.0` and minimum `Microsoft.EntityFrameworkCore` version is `v2.0.0`.
+
+If you use this library in your project, please note to use same `nuget versions`! The `DbContextScope` uses `Microsoft.EntityFrameworkCore.Relational v2.0.0`!
+
+Example `*.csproj` to use `v3.1.1`:
+
+```xml
+
+
```
-2017-09-29 - tncalvert
-This is a fork to support Entity Framework Core.
-As of right now, the project targets .NET 4.6.1.
-```
+## Nuget
+
+[![NuGet](https://img.shields.io/nuget/v/DoJo.EntityFrameworkCore.DbContextScope.svg "nuget")](https://www.nuget.org/packages/DoJo.EntityFrameworkCore.DbContextScope)
+[![NuGetDownloads](https://img.shields.io/nuget/dt/DoJo.EntityFrameworkCore.DbContextScope.svg "nuget downloads")](https://www.nuget.org/packages/DoJo.EntityFrameworkCore.DbContextScope)
+
+A NuGet-package is available [here](https://nuget.org/packages/DoJo.EntityFrameworkCore.DbContextScope/).
+
+
+## Contributors
+
+Feel free to make a PullRequest or open an Issue to extend this library!
+
+# DbContextScope
A simple and flexible way to manage your Entity Framework DbContext instances.
`DbContextScope` was created out of the need for a better way to manage DbContext instances in Entity Framework-based applications.
diff --git a/createBranch.ps1 b/createBranch.ps1
new file mode 100644
index 0000000..2dd14a5
--- /dev/null
+++ b/createBranch.ps1
@@ -0,0 +1,31 @@
+param([string]$IssueName)
+
+$branchName = $IssueName
+
+###########################################################
+# clean input string #
+###########################################################
+
+# character replacement
+$rWhiteSpace = [regex]'[ ]{1,}'
+$rSlash = [regex]'[/]{1,}'
+$rBackSlash = [regex]'[\\]{1,}'
+$rSeperator = [regex]'[-]{2,}'
+
+$branchName = $rWhiteSpace.Replace($branchName, "-")
+$branchName = $rSlash.Replace($branchName, "-")
+$branchName = $rBackSlash.Replace($branchName, "-")
+$branchName = $rSeperator.Replace($branchName, "-")
+$branchName = $branchName.Trim('-')
+
+# limit bracnhname to 40 characters length
+$branchName = $branchName.Substring('0', '40')
+
+echo "New branch '$branchName' created"
+
+git checkout -b $branchName
+git add .
+git add -u
+
+#git commit -m "Description of my changes for issue $IssueName"
+#git push -u origin $branchName
\ No newline at end of file
diff --git a/Demo/BusinessLogicServices/UserCreationService.cs b/src/DbContextScope.EF2.Test/BusinessLogicServices/UserCreationService.cs
similarity index 94%
rename from Demo/BusinessLogicServices/UserCreationService.cs
rename to src/DbContextScope.EF2.Test/BusinessLogicServices/UserCreationService.cs
index 31ab13c..ece6d66 100644
--- a/Demo/BusinessLogicServices/UserCreationService.cs
+++ b/src/DbContextScope.EF2.Test/BusinessLogicServices/UserCreationService.cs
@@ -1,10 +1,9 @@
-using EntityFrameworkCore.DbContextScope;
-using Numero3.EntityFramework.Demo.CommandModel;
-using Numero3.EntityFramework.Demo.DomainModel;
-using Numero3.EntityFramework.Demo.Repositories;
-using System;
+using System;
+using EntityFrameworkCore.DbContextScope.Test.CommandModel;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+using EntityFrameworkCore.DbContextScope.Test.Repositories;
-namespace Numero3.EntityFramework.Demo.BusinessLogicServices {
+namespace EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices {
/*
* Example business logic service implementing command functionalities (i.e. create / update actions).
*/
@@ -39,6 +38,7 @@ public void CreateUser(UserCreationSpec userToCreate) {
//-- Persist
_userRepository.Add(user);
+
dbContextScope.SaveChanges();
}
}
diff --git a/Demo/BusinessLogicServices/UserCreditScoreService.cs b/src/DbContextScope.EF2.Test/BusinessLogicServices/UserCreditScoreService.cs
similarity index 94%
rename from Demo/BusinessLogicServices/UserCreditScoreService.cs
rename to src/DbContextScope.EF2.Test/BusinessLogicServices/UserCreditScoreService.cs
index f080d4a..85be0fc 100644
--- a/Demo/BusinessLogicServices/UserCreditScoreService.cs
+++ b/src/DbContextScope.EF2.Test/BusinessLogicServices/UserCreditScoreService.cs
@@ -1,11 +1,10 @@
-using EntityFrameworkCore.DbContextScope;
-using Numero3.EntityFramework.Demo.DatabaseContext;
-using System;
+using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
-namespace Numero3.EntityFramework.Demo.BusinessLogicServices {
+namespace EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices {
public class UserCreditScoreService {
private readonly IDbContextScopeFactory _dbContextScopeFactory;
diff --git a/Demo/BusinessLogicServices/UserEmailService.cs b/src/DbContextScope.EF2.Test/BusinessLogicServices/UserEmailService.cs
similarity index 93%
rename from Demo/BusinessLogicServices/UserEmailService.cs
rename to src/DbContextScope.EF2.Test/BusinessLogicServices/UserEmailService.cs
index cda07e7..44b90d3 100644
--- a/Demo/BusinessLogicServices/UserEmailService.cs
+++ b/src/DbContextScope.EF2.Test/BusinessLogicServices/UserEmailService.cs
@@ -1,10 +1,9 @@
-using EntityFrameworkCore.DbContextScope;
-using Numero3.EntityFramework.Demo.DatabaseContext;
-using Numero3.EntityFramework.Demo.DomainModel;
-using System;
+using System;
using System.Collections.Generic;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
-namespace Numero3.EntityFramework.Demo.BusinessLogicServices {
+namespace EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices {
public class UserEmailService {
private readonly IDbContextScopeFactory _dbContextScopeFactory;
diff --git a/Demo/BusinessLogicServices/UserQueryService.cs b/src/DbContextScope.EF2.Test/BusinessLogicServices/UserQueryService.cs
similarity index 95%
rename from Demo/BusinessLogicServices/UserQueryService.cs
rename to src/DbContextScope.EF2.Test/BusinessLogicServices/UserQueryService.cs
index 5a983ab..5857a80 100644
--- a/Demo/BusinessLogicServices/UserQueryService.cs
+++ b/src/DbContextScope.EF2.Test/BusinessLogicServices/UserQueryService.cs
@@ -1,14 +1,13 @@
-using EntityFrameworkCore.DbContextScope;
-using Numero3.EntityFramework.Demo.DatabaseContext;
-using Numero3.EntityFramework.Demo.DomainModel;
-using Numero3.EntityFramework.Demo.Repositories;
-using System;
+using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+using EntityFrameworkCore.DbContextScope.Test.Repositories;
-namespace Numero3.EntityFramework.Demo.BusinessLogicServices {
+namespace EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices {
/*
* Example business logic service implementing query functionalities (i.e. read actions).
*/
diff --git a/Demo/CommandModel/UserCreationSpec.cs b/src/DbContextScope.EF2.Test/CommandModel/UserCreationSpec.cs
similarity index 91%
rename from Demo/CommandModel/UserCreationSpec.cs
rename to src/DbContextScope.EF2.Test/CommandModel/UserCreationSpec.cs
index fdab605..05cfc47 100644
--- a/Demo/CommandModel/UserCreationSpec.cs
+++ b/src/DbContextScope.EF2.Test/CommandModel/UserCreationSpec.cs
@@ -1,6 +1,6 @@
using System;
-namespace Numero3.EntityFramework.Demo.CommandModel {
+namespace EntityFrameworkCore.DbContextScope.Test.CommandModel {
///
/// Specifications of the CreateUser command. Defines the properties of a new user.
///
diff --git a/Demo/DatabaseContext/UserManagementDbContext.cs b/src/DbContextScope.EF2.Test/DatabaseContext/UserManagementDbContext.cs
similarity index 78%
rename from Demo/DatabaseContext/UserManagementDbContext.cs
rename to src/DbContextScope.EF2.Test/DatabaseContext/UserManagementDbContext.cs
index 66c0c1e..fe4112d 100644
--- a/Demo/DatabaseContext/UserManagementDbContext.cs
+++ b/src/DbContextScope.EF2.Test/DatabaseContext/UserManagementDbContext.cs
@@ -1,7 +1,7 @@
-using Microsoft.EntityFrameworkCore;
-using Numero3.EntityFramework.Demo.DomainModel;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+using Microsoft.EntityFrameworkCore;
-namespace Numero3.EntityFramework.Demo.DatabaseContext {
+namespace EntityFrameworkCore.DbContextScope.Test.DatabaseContext {
public class UserManagementDbContext : DbContext {
// Map our 'User' model by convention
public DbSet Users { get; set; }
diff --git a/src/DbContextScope.EF2.Test/DbContextScope.EF2.Test.csproj b/src/DbContextScope.EF2.Test/DbContextScope.EF2.Test.csproj
new file mode 100644
index 0000000..b19401b
--- /dev/null
+++ b/src/DbContextScope.EF2.Test/DbContextScope.EF2.Test.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net461;netcoreapp2.0;netcoreapp3.0
+ false
+ EntityFrameworkCore.DbContextScope.Test
+ DoJo.EntityFrameworkCore.DbContextScope.Test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DbContextScope.EF2.Test/DbContextScopeTest.cs b/src/DbContextScope.EF2.Test/DbContextScopeTest.cs
new file mode 100644
index 0000000..2e70509
--- /dev/null
+++ b/src/DbContextScope.EF2.Test/DbContextScopeTest.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Linq;
+using EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices;
+using EntityFrameworkCore.DbContextScope.Test.CommandModel;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+using EntityFrameworkCore.DbContextScope.Test.Repositories;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace EntityFrameworkCore.DbContextScope.Test
+{
+ public static class UserSpecExtensions
+ {
+ public static void Equal(this UserCreationSpec spec, User user)
+ {
+ Assert.NotNull(spec);
+ Assert.NotNull(user);
+ Assert.Equal(spec.Id, user.Id);
+ Assert.Equal(spec.Email, user.Email);
+ Assert.Equal(spec.Name, user.Name);
+ }
+ }
+
+ public class DbContextScopeTest
+ {
+ private readonly ITestOutputHelper _Output;
+
+ public DbContextScopeTest(ITestOutputHelper output)
+ {
+ _Output = output;
+ }
+
+ private class DbContextFactory : IDbContextFactory
+ {
+ public TDbContext CreateDbContext() where TDbContext : DbContext
+ {
+ if (typeof(TDbContext) == typeof(UserManagementDbContext))
+ {
+ var config = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase("1337")
+ .ConfigureWarnings(warnings => { warnings.Ignore(InMemoryEventId.TransactionIgnoredWarning); });
+ return new UserManagementDbContext(config.Options) as TDbContext;
+ }
+
+ throw new NotImplementedException(typeof(TDbContext).Name);
+ }
+ }
+
+ [Fact]
+ public void FullTest()
+ {
+ //-- Poor-man DI - build our dependencies by hand for this demo
+ var dbContextScopeFactory = new DbContextScopeFactory(new DbContextFactory());
+ var ambientDbContextLocator = new AmbientDbContextLocator();
+ var userRepository = new UserRepository(ambientDbContextLocator);
+
+ var userCreationService = new UserCreationService(dbContextScopeFactory, userRepository);
+ var userQueryService = new UserQueryService(dbContextScopeFactory, userRepository);
+ var userEmailService = new UserEmailService(dbContextScopeFactory);
+ var userCreditScoreService = new UserCreditScoreService(dbContextScopeFactory);
+
+ _Output.WriteLine(
+ "This demo uses an EF Core In Memory database. It does not create any external databases.");
+ _Output.WriteLine("");
+
+ //-- Demo of typical usage for read and writes
+ _Output.WriteLine("Creating a user called Mary...");
+ var marysSpec = new UserCreationSpec("Mary", "mary@example.com");
+ userCreationService.CreateUser(marysSpec);
+ _Output.WriteLine("Done.\n");
+
+ _Output.WriteLine("Trying to retrieve our newly created user from the data store...");
+ var mary = userQueryService.GetUser(marysSpec.Id);
+ _Output.WriteLine("OK. Persisted user: {0}", mary);
+ marysSpec.Equal(mary);
+
+ //-- Demo of nested DbContextScopes
+ _Output.WriteLine("Creating 2 new users called John and Jeanne in an atomic transaction...");
+ var johnSpec = new UserCreationSpec("John", "john@example.com");
+ var jeanneSpec = new UserCreationSpec("Jeanne", "jeanne@example.com");
+ userCreationService.CreateListOfUsers(johnSpec, jeanneSpec);
+ _Output.WriteLine("Done.\n");
+
+ _Output.WriteLine("Trying to retrieve our newly created users from the data store...");
+ var createdUsers = userQueryService.GetUsers(johnSpec.Id, jeanneSpec.Id).ToList();
+ _Output.WriteLine("OK. Found {0} persisted users.", createdUsers.Count);
+
+ Assert.Equal(2, createdUsers.Count);
+ johnSpec.Equal(createdUsers[0]);
+ jeanneSpec.Equal(createdUsers[1]);
+
+ //-- Demo of nested DbContextScopes in the face of an exception.
+ // If any of the provided users failed to get persisted, none should get persisted.
+ _Output.WriteLine(
+ "Creating 2 new users called Julie and Marc in an atomic transaction. Will make the persistence of the second user fail intentionally in order to test the atomicity of the transaction...");
+ var julieSpec = new UserCreationSpec("Julie", "julie@example.com");
+ var marcSpec = new UserCreationSpec("Marc", "marc@example.com");
+
+ Assert.ThrowsAny(() =>
+ {
+ userCreationService.CreateListOfUsersWithIntentionalFailure(julieSpec, marcSpec);
+ });
+
+ _Output.WriteLine("Trying to retrieve our newly created users from the data store...");
+ var maybeCreatedUsers = userQueryService.GetUsers(julieSpec.Id, marcSpec.Id).ToList();
+ _Output.WriteLine(
+ "Found {0} persisted users. If this number is 0, we're all good. If this number is not 0, we have a big problem.",
+ maybeCreatedUsers.Count);
+ Assert.Equal(0, maybeCreatedUsers.Count);
+
+ //-- Demo of DbContextScope within an async flow
+ _Output.WriteLine("Trying to retrieve two users John and Jeanne sequentially in an asynchronous manner...");
+ // We're going to block on the async task here as we don't have a choice. No risk of deadlocking in any case as console apps
+ // don't have a synchronization context.
+ var usersFoundAsync = userQueryService.GetTwoUsersAsync(johnSpec.Id, jeanneSpec.Id).Result;
+ _Output.WriteLine("OK. Found {0} persisted users.", usersFoundAsync.Count);
+ Assert.Equal(2, usersFoundAsync.Count);
+ johnSpec.Equal(usersFoundAsync[0]);
+ jeanneSpec.Equal(usersFoundAsync[1]);
+
+ //-- Demo of explicit database transaction.
+ _Output.WriteLine("Trying to retrieve user John within a READ UNCOMMITTED database transaction...");
+ // You'll want to use SQL Profiler or Entity Framework Profiler to verify that the correct transaction isolation
+ // level is being used.
+ var userMaybeUncommitted = userQueryService.GetUserUncommitted(johnSpec.Id);
+ _Output.WriteLine("OK. User found: {0}", userMaybeUncommitted);
+ johnSpec.Equal(userMaybeUncommitted);
+
+ //-- Demo of disabling the DbContextScope nesting behaviour in order to force the persistence of changes made to entities
+ // This is a pretty advanced feature that you can safely ignore until you actually need it.
+ _Output.WriteLine("Will simulate sending a Welcome email to John...");
+
+ using (var parentScope = dbContextScopeFactory.Create())
+ {
+ var parentDbContext = parentScope.DbContexts.Get();
+
+ // Load John in the parent DbContext
+ var john = parentDbContext.Users.Find(johnSpec.Id);
+ _Output.WriteLine("Before calling SendWelcomeEmail(), john.WelcomeEmailSent = " +
+ john.WelcomeEmailSent);
+
+ // Now call our SendWelcomeEmail() business logic service method, which will
+ // update John in a non-nested child context
+ userEmailService.SendWelcomeEmail(johnSpec.Id);
+
+ // Verify that we can see the modifications made to John by the SendWelcomeEmail() method
+ _Output.WriteLine("After calling SendWelcomeEmail(), john.WelcomeEmailSent = " +
+ john.WelcomeEmailSent);
+
+ // Note that even though we're not calling SaveChanges() in the parent scope here, the changes
+ // made to John by SendWelcomeEmail() will remain persisted in the database as SendWelcomeEmail()
+ // forced the creation of a new DbContextScope.
+ }
+
+ //-- Demonstration of DbContextScope and parallel programming
+ _Output.WriteLine(
+ "Calculating and storing the credit score of all users in the database in parallel...");
+ userCreditScoreService.UpdateCreditScoreForAllUsers();
+ _Output.WriteLine("Done.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/DomainModel/User.cs b/src/DbContextScope.EF2.Test/DomainModel/User.cs
similarity index 91%
rename from Demo/DomainModel/User.cs
rename to src/DbContextScope.EF2.Test/DomainModel/User.cs
index eabc905..ed9e808 100644
--- a/Demo/DomainModel/User.cs
+++ b/src/DbContextScope.EF2.Test/DomainModel/User.cs
@@ -1,6 +1,6 @@
using System;
-namespace Numero3.EntityFramework.Demo.DomainModel {
+namespace EntityFrameworkCore.DbContextScope.Test.DomainModel {
// Anemic model to keep this demo application simple.
public class User {
public Guid Id { get; set; }
diff --git a/Demo/Repositories/IUserRepository.cs b/src/DbContextScope.EF2.Test/Repositories/IUserRepository.cs
similarity index 55%
rename from Demo/Repositories/IUserRepository.cs
rename to src/DbContextScope.EF2.Test/Repositories/IUserRepository.cs
index 30aa8e7..6493fab 100644
--- a/Demo/Repositories/IUserRepository.cs
+++ b/src/DbContextScope.EF2.Test/Repositories/IUserRepository.cs
@@ -1,8 +1,8 @@
-using Numero3.EntityFramework.Demo.DomainModel;
-using System;
+using System;
using System.Threading.Tasks;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
-namespace Numero3.EntityFramework.Demo.Repositories {
+namespace EntityFrameworkCore.DbContextScope.Test.Repositories {
public interface IUserRepository {
User Get(Guid userId);
Task GetAsync(Guid userId);
diff --git a/Demo/Repositories/UserRepository.cs b/src/DbContextScope.EF2.Test/Repositories/UserRepository.cs
similarity index 85%
rename from Demo/Repositories/UserRepository.cs
rename to src/DbContextScope.EF2.Test/Repositories/UserRepository.cs
index 9d3e5fe..e93b165 100644
--- a/Demo/Repositories/UserRepository.cs
+++ b/src/DbContextScope.EF2.Test/Repositories/UserRepository.cs
@@ -1,10 +1,9 @@
-using EntityFrameworkCore.DbContextScope;
-using Numero3.EntityFramework.Demo.DatabaseContext;
-using Numero3.EntityFramework.Demo.DomainModel;
-using System;
+using System;
using System.Threading.Tasks;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
-namespace Numero3.EntityFramework.Demo.Repositories {
+namespace EntityFrameworkCore.DbContextScope.Test.Repositories {
/*
* An example "repository" relying on an ambient DbContext instance.
*
@@ -43,8 +42,16 @@ public User Get(Guid userId) {
return DbContext.Users.Find(userId);
}
- public Task GetAsync(Guid userId) {
+ public Task GetAsync(Guid userId)
+ {
return DbContext.Users.FindAsync(userId);
+
+//#if NETCOREAPP2_0 || NET461
+// return DbContext.Users.FindAsync(userId);
+//#endif
+//#if NETCOREAPP3_0
+// return DbContext.Users.FindAsync(userId).AsTask();
+//#endif
}
public void Add(User user) {
diff --git a/src/DbContextScope.EF3.Test/BusinessLogicServices/UserCreationService.cs b/src/DbContextScope.EF3.Test/BusinessLogicServices/UserCreationService.cs
new file mode 100644
index 0000000..ece6d66
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/BusinessLogicServices/UserCreationService.cs
@@ -0,0 +1,110 @@
+using System;
+using EntityFrameworkCore.DbContextScope.Test.CommandModel;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+using EntityFrameworkCore.DbContextScope.Test.Repositories;
+
+namespace EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices {
+ /*
+ * Example business logic service implementing command functionalities (i.e. create / update actions).
+ */
+ public class UserCreationService {
+ private readonly IDbContextScopeFactory _dbContextScopeFactory;
+ private readonly IUserRepository _userRepository;
+
+ public UserCreationService(IDbContextScopeFactory dbContextScopeFactory, IUserRepository userRepository) {
+ _dbContextScopeFactory = dbContextScopeFactory ?? throw new ArgumentNullException("dbContextScopeFactory");
+ _userRepository = userRepository ?? throw new ArgumentNullException("userRepository");
+ }
+
+ public void CreateUser(UserCreationSpec userToCreate) {
+ if (userToCreate == null)
+ throw new ArgumentNullException("userToCreate");
+
+ userToCreate.Validate();
+
+ /*
+ * Typical usage of DbContextScope for a read-write business transaction.
+ * It's as simple as it looks.
+ */
+ using (var dbContextScope = _dbContextScopeFactory.Create()) {
+ //-- Build domain model
+ var user = new User() {
+ Id = userToCreate.Id,
+ Name = userToCreate.Name,
+ Email = userToCreate.Email,
+ WelcomeEmailSent = false,
+ CreatedOn = DateTime.UtcNow
+ };
+
+ //-- Persist
+ _userRepository.Add(user);
+
+ dbContextScope.SaveChanges();
+ }
+ }
+
+ public void CreateListOfUsers(params UserCreationSpec[] usersToCreate) {
+ /*
+ * Example of DbContextScope nesting in action.
+ *
+ * We already have a service method - CreateUser() - that knows how to create a new user
+ * and implements all the business rules around the creation of a new user
+ * (e.g. validation, initialization, sending notifications to other domain model objects...).
+ *
+ * So we'll just call it in a loop to create the list of new users we've
+ * been asked to create.
+ *
+ * Of course, since this is a business logic service method, we are making
+ * an implicit guarantee to whoever is calling us that the changes we make to
+ * the system will be either committed or rolled-back in an atomic manner.
+ * I.e. either all the users we've been asked to create will get persisted
+ * or none of them will. It would be disastrous to have a partial failure here
+ * and end up with some users but not all having been created.
+ *
+ * DbContextScope makes this trivial to implement.
+ *
+ * The inner DbContextScope instance that the CreateUser() method creates
+ * will join our top-level scope. This ensures that the same DbContext instance is
+ * going to be used throughout this business transaction.
+ *
+ */
+
+ using (var dbContextScope = _dbContextScopeFactory.Create()) {
+ foreach (var toCreate in usersToCreate) {
+ CreateUser(toCreate);
+ }
+
+ // All the changes will get persisted here
+ dbContextScope.SaveChanges();
+ }
+ }
+
+ public void CreateListOfUsersWithIntentionalFailure(params UserCreationSpec[] usersToCreate) {
+ /*
+ * Here, we'll verify that inner DbContextScopes really join the parent scope and
+ * don't persist their changes until the parent scope completes successfully.
+ */
+
+ var firstUser = true;
+
+ using (var dbContextScope = _dbContextScopeFactory.Create()) {
+ foreach (var toCreate in usersToCreate) {
+ if (firstUser) {
+ CreateUser(toCreate);
+ Console.WriteLine("Successfully created a new User named '{0}'.", toCreate.Name);
+ firstUser = false;
+ } else {
+ // OK. So we've successfully persisted one user.
+ // We're going to simulate a failure when attempting to
+ // persist the second user and see what ends up getting
+ // persisted in the DB.
+ throw new Exception(String.Format("Oh no! An error occurred when attempting to create user named '{0}' in our database.", toCreate.Name));
+ }
+ }
+
+ dbContextScope.SaveChanges();
+ }
+ }
+ }
+}
+
diff --git a/src/DbContextScope.EF3.Test/BusinessLogicServices/UserCreditScoreService.cs b/src/DbContextScope.EF3.Test/BusinessLogicServices/UserCreditScoreService.cs
new file mode 100644
index 0000000..85be0fc
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/BusinessLogicServices/UserCreditScoreService.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+
+namespace EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices {
+ public class UserCreditScoreService {
+ private readonly IDbContextScopeFactory _dbContextScopeFactory;
+
+ public UserCreditScoreService(IDbContextScopeFactory dbContextScopeFactory) {
+ _dbContextScopeFactory = dbContextScopeFactory ?? throw new ArgumentNullException("dbContextScopeFactory");
+ }
+
+ public void UpdateCreditScoreForAllUsers() {
+ /*
+ * Demo of DbContextScope + parallel programming.
+ */
+
+ using (var dbContextScope = _dbContextScopeFactory.Create()) {
+ //-- Get all users
+ var dbContext = dbContextScope.DbContexts.Get();
+ var userIds = dbContext.Users.Select(u => u.Id).ToList();
+
+ Console.WriteLine("Found {0} users in the database. Will calculate and store their credit scores in parallel.", userIds.Count);
+
+ //-- Calculate and store the credit score of each user
+ // We're going to imagine that calculating a credit score of a user takes some time.
+ // So we'll do it in parallel.
+
+ // You MUST call SuppressAmbientContext() when kicking off a parallel execution flow
+ // within a DbContextScope. Otherwise, this DbContextScope will remain the ambient scope
+ // in the parallel flows of execution, potentially leading to multiple threads
+ // accessing the same DbContext instance.
+ using (_dbContextScopeFactory.SuppressAmbientContext()) {
+ Parallel.ForEach(userIds, UpdateCreditScore);
+ }
+
+ // Note: SaveChanges() isn't going to do anything in this instance since all the changes
+ // were actually made and saved in separate DbContextScopes created in separate threads.
+ dbContextScope.SaveChanges();
+ }
+ }
+
+ public void UpdateCreditScore(Guid userId) {
+ using (var dbContextScope = _dbContextScopeFactory.Create()) {
+ var dbContext = dbContextScope.DbContexts.Get();
+ var user = dbContext.Users.Find(userId);
+ if (user == null)
+ throw new ArgumentException(String.Format("Invalid userId provided: {0}. Couldn't find a User with this ID.", userId));
+
+ // Simulate the calculation of a credit score taking some time
+ var random = new Random(Thread.CurrentThread.ManagedThreadId);
+ Thread.Sleep(random.Next(300, 1000));
+
+ user.CreditScore = random.Next(1, 100);
+ dbContextScope.SaveChanges();
+ }
+ }
+ }
+}
diff --git a/src/DbContextScope.EF3.Test/BusinessLogicServices/UserEmailService.cs b/src/DbContextScope.EF3.Test/BusinessLogicServices/UserEmailService.cs
new file mode 100644
index 0000000..44b90d3
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/BusinessLogicServices/UserEmailService.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+
+namespace EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices {
+ public class UserEmailService {
+ private readonly IDbContextScopeFactory _dbContextScopeFactory;
+
+ public UserEmailService(IDbContextScopeFactory dbContextScopeFactory) {
+ _dbContextScopeFactory = dbContextScopeFactory ?? throw new ArgumentNullException("dbContextScopeFactory");
+ }
+
+ public void SendWelcomeEmail(Guid userId) {
+ /*
+ * Demo of forcing the creation of a new DbContextScope
+ * to ensure that changes made to the model in this service
+ * method are persisted even if that method happens to get
+ * called within the scope of a wider business transaction
+ * that eventually fails for any reason.
+ *
+ * This is an advanced feature that should be used as rarely
+ * as possible (and ideally, never).
+ */
+
+ // We're going to send a welcome email to the provided user
+ // (if one hasn't been sent already). Once sent, we'll update
+ // that User entity in our DB to record that its Welcome email
+ // has been sent.
+
+ // Emails can't be rolled-back. Once they're sent, they're sent.
+ // So once the email has been sent successfully, we absolutely
+ // must persist this fact in our DB. Even if that method is called
+ // by another busines logic service method as part of a wider
+ // business transaction and even if that parent business transaction
+ // ends up failing for any reason, we still must ensure that
+ // we have recorded the fact that the Welcome email has been sent.
+ // Otherwise, we would risk spamming our users with repeated Welcome
+ // emails.
+
+ // Force the creation of a new DbContextScope so that the changes we make here are
+ // guaranteed to get persisted regardless of what happens after this method has completed.
+ using (var dbContextScope = _dbContextScopeFactory.Create(DbContextScopeOption.ForceCreateNew)) {
+ var dbContext = dbContextScope.DbContexts.Get();
+ var user = dbContext.Users.Find(userId);
+
+ if (user == null)
+ throw new ArgumentException(String.Format("Invalid userId provided: {0}. Couldn't find a User with this ID.", userId));
+
+ if (!user.WelcomeEmailSent) {
+ SendEmail(user.Email);
+ user.WelcomeEmailSent = true;
+ }
+
+ dbContextScope.SaveChanges();
+
+ // When you force the creation of a new DbContextScope, you must force the parent
+ // scope (if any) to reload the entities you've modified here. Otherwise, the method calling
+ // you might not be able to see the changes you made here.
+ dbContextScope.RefreshEntitiesInParentScope(new List { user });
+ }
+ }
+
+ private void SendEmail(string emailAddress) {
+ // Send the email synchronously. Throw if any error occurs.
+ // [...]
+ }
+ }
+}
diff --git a/src/DbContextScope.EF3.Test/BusinessLogicServices/UserQueryService.cs b/src/DbContextScope.EF3.Test/BusinessLogicServices/UserQueryService.cs
new file mode 100644
index 0000000..5857a80
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/BusinessLogicServices/UserQueryService.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+using EntityFrameworkCore.DbContextScope.Test.Repositories;
+
+namespace EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices {
+ /*
+ * Example business logic service implementing query functionalities (i.e. read actions).
+ */
+ public class UserQueryService {
+ private readonly IDbContextScopeFactory _dbContextScopeFactory;
+ private readonly IUserRepository _userRepository;
+
+ public UserQueryService(IDbContextScopeFactory dbContextScopeFactory, IUserRepository userRepository) {
+ _dbContextScopeFactory = dbContextScopeFactory ?? throw new ArgumentNullException("dbContextScopeFactory");
+ _userRepository = userRepository ?? throw new ArgumentNullException("userRepository");
+ }
+
+ public User GetUser(Guid userId) {
+ /*
+ * An example of using DbContextScope for read-only queries.
+ * Here, we access the Entity Framework DbContext directly from
+ * the business logic service class.
+ *
+ * Calling SaveChanges() is not necessary here (and in fact not
+ * possible) since we created a read-only scope.
+ */
+ using (var dbContextScope = _dbContextScopeFactory.CreateReadOnly()) {
+ var dbContext = dbContextScope.DbContexts.Get();
+ var user = dbContext.Users.Find(userId);
+
+ if (user == null)
+ throw new ArgumentException(String.Format("Invalid value provided for userId: [{0}]. Couldn't find a user with this ID.", userId));
+
+ return user;
+ }
+ }
+
+ public IEnumerable GetUsers(params Guid[] userIds) {
+ using (var dbContextScope = _dbContextScopeFactory.CreateReadOnly()) {
+ var dbContext = dbContextScope.DbContexts.Get();
+ return dbContext.Users.Where(u => userIds.Contains(u.Id)).ToList();
+ }
+ }
+
+ public User GetUserViaRepository(Guid userId) {
+ /*
+ * Same as GetUsers() but using a repository layer instead of accessing the
+ * EF DbContext directly.
+ *
+ * Note how we don't have to worry about knowing what type of DbContext the
+ * repository will need, about creating the DbContext instance or about passing
+ * DbContext instances around.
+ *
+ * The DbContextScope will take care of creating the necessary DbContext instances
+ * and making them available as ambient contexts for our repository layer to use.
+ * It will also guarantee that only one instance of any given DbContext type exists
+ * within its scope ensuring that all persistent entities managed within that scope
+ * are attached to the same DbContext.
+ */
+ using (_dbContextScopeFactory.CreateReadOnly()) {
+ var user = _userRepository.Get(userId);
+
+ if (user == null)
+ throw new ArgumentException(String.Format("Invalid value provided for userId: [{0}]. Couldn't find a user with this ID.", userId));
+
+ return user;
+ }
+ }
+
+ public async Task> GetTwoUsersAsync(Guid userId1, Guid userId2) {
+ /*
+ * A very contrived example of ambient DbContextScope within an async flow.
+ *
+ * Note that the ConfigureAwait(false) calls here aren't strictly necessary
+ * and are unrelated to DbContextScope. You can remove them if you want and
+ * the code will run in the same way. It is however good practice to configure
+ * all your awaitables in library code to not continue
+ * on the captured synchronization context. It avoids having to pay the overhead
+ * of capturing the sync context and running the task continuation on it when
+ * library code doesn't need that context. If also helps prevent potential deadlocks
+ * if the upstream code has been poorly written and blocks on async tasks.
+ *
+ * "Library code" is any code in layers under the presentation tier. Typically any code
+ * other that code in ASP.NET MVC / WebApi controllers or Window Form / WPF forms.
+ *
+ * See http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx for
+ * more details.
+ */
+
+ using (_dbContextScopeFactory.CreateReadOnly()) {
+ var user1 = await _userRepository.GetAsync(userId1).ConfigureAwait(false);
+
+ // We're now in the continuation of the first async task. This is most
+ // likely executing in a thread from the ThreadPool, i.e. in a different
+ // thread that the one where we created our DbContextScope. Our ambient
+ // DbContextScope is still available here however, which allows the call
+ // below to succeed.
+
+ var user2 = await _userRepository.GetAsync(userId2).ConfigureAwait(false);
+
+ // In other words, DbContextScope works with async execution flow as you'd expect:
+ // It Just Works.
+
+ return new List { user1, user2 }.Where(u => u != null).ToList();
+ }
+ }
+
+ public User GetUserUncommitted(Guid userId) {
+ /*
+ * An example of explicit database transaction.
+ *
+ * Read the comment for CreateReadOnlyWithTransaction() before using this overload
+ * as there are gotchas when doing this!
+ */
+ using (_dbContextScopeFactory.CreateReadOnlyWithTransaction(IsolationLevel.ReadUncommitted)) {
+ return _userRepository.Get(userId);
+ }
+ }
+ }
+}
diff --git a/src/DbContextScope.EF3.Test/CommandModel/UserCreationSpec.cs b/src/DbContextScope.EF3.Test/CommandModel/UserCreationSpec.cs
new file mode 100644
index 0000000..05cfc47
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/CommandModel/UserCreationSpec.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace EntityFrameworkCore.DbContextScope.Test.CommandModel {
+ ///
+ /// Specifications of the CreateUser command. Defines the properties of a new user.
+ ///
+ public class UserCreationSpec {
+ ///
+ /// The Id automatically generated for this user.
+ ///
+ public Guid Id { get; protected set; }
+
+ public string Name { get; protected set; }
+ public string Email { get; protected set; }
+
+ public UserCreationSpec(string name, string email) {
+ Id = Guid.NewGuid();
+ Name = name;
+ Email = email;
+ }
+
+ public void Validate() {
+ // [...]
+ }
+ }
+}
diff --git a/src/DbContextScope.EF3.Test/DatabaseContext/UserManagementDbContext.cs b/src/DbContextScope.EF3.Test/DatabaseContext/UserManagementDbContext.cs
new file mode 100644
index 0000000..fe4112d
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/DatabaseContext/UserManagementDbContext.cs
@@ -0,0 +1,21 @@
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+using Microsoft.EntityFrameworkCore;
+
+namespace EntityFrameworkCore.DbContextScope.Test.DatabaseContext {
+ public class UserManagementDbContext : DbContext {
+ // Map our 'User' model by convention
+ public DbSet Users { get; set; }
+
+ public UserManagementDbContext(DbContextOptions options)
+ : base(options) { }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder) {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity(builder => {
+ builder.Property(m => m.Name).IsRequired();
+ builder.Property(m => m.Email).IsRequired();
+ });
+ }
+ }
+}
diff --git a/src/DbContextScope.EF3.Test/DbContextScope.EF3.Test.csproj b/src/DbContextScope.EF3.Test/DbContextScope.EF3.Test.csproj
new file mode 100644
index 0000000..24cf54c
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/DbContextScope.EF3.Test.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net461;netcoreapp2.0;netcoreapp3.0
+ false
+ EntityFrameworkCore.DbContextScope.Test
+ DoJo.EntityFrameworkCore.DbContextScope.Test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DbContextScope.EF3.Test/DbContextScopeTest.cs b/src/DbContextScope.EF3.Test/DbContextScopeTest.cs
new file mode 100644
index 0000000..2e70509
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/DbContextScopeTest.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Linq;
+using EntityFrameworkCore.DbContextScope.Test.BusinessLogicServices;
+using EntityFrameworkCore.DbContextScope.Test.CommandModel;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+using EntityFrameworkCore.DbContextScope.Test.Repositories;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace EntityFrameworkCore.DbContextScope.Test
+{
+ public static class UserSpecExtensions
+ {
+ public static void Equal(this UserCreationSpec spec, User user)
+ {
+ Assert.NotNull(spec);
+ Assert.NotNull(user);
+ Assert.Equal(spec.Id, user.Id);
+ Assert.Equal(spec.Email, user.Email);
+ Assert.Equal(spec.Name, user.Name);
+ }
+ }
+
+ public class DbContextScopeTest
+ {
+ private readonly ITestOutputHelper _Output;
+
+ public DbContextScopeTest(ITestOutputHelper output)
+ {
+ _Output = output;
+ }
+
+ private class DbContextFactory : IDbContextFactory
+ {
+ public TDbContext CreateDbContext() where TDbContext : DbContext
+ {
+ if (typeof(TDbContext) == typeof(UserManagementDbContext))
+ {
+ var config = new DbContextOptionsBuilder()
+ .UseInMemoryDatabase("1337")
+ .ConfigureWarnings(warnings => { warnings.Ignore(InMemoryEventId.TransactionIgnoredWarning); });
+ return new UserManagementDbContext(config.Options) as TDbContext;
+ }
+
+ throw new NotImplementedException(typeof(TDbContext).Name);
+ }
+ }
+
+ [Fact]
+ public void FullTest()
+ {
+ //-- Poor-man DI - build our dependencies by hand for this demo
+ var dbContextScopeFactory = new DbContextScopeFactory(new DbContextFactory());
+ var ambientDbContextLocator = new AmbientDbContextLocator();
+ var userRepository = new UserRepository(ambientDbContextLocator);
+
+ var userCreationService = new UserCreationService(dbContextScopeFactory, userRepository);
+ var userQueryService = new UserQueryService(dbContextScopeFactory, userRepository);
+ var userEmailService = new UserEmailService(dbContextScopeFactory);
+ var userCreditScoreService = new UserCreditScoreService(dbContextScopeFactory);
+
+ _Output.WriteLine(
+ "This demo uses an EF Core In Memory database. It does not create any external databases.");
+ _Output.WriteLine("");
+
+ //-- Demo of typical usage for read and writes
+ _Output.WriteLine("Creating a user called Mary...");
+ var marysSpec = new UserCreationSpec("Mary", "mary@example.com");
+ userCreationService.CreateUser(marysSpec);
+ _Output.WriteLine("Done.\n");
+
+ _Output.WriteLine("Trying to retrieve our newly created user from the data store...");
+ var mary = userQueryService.GetUser(marysSpec.Id);
+ _Output.WriteLine("OK. Persisted user: {0}", mary);
+ marysSpec.Equal(mary);
+
+ //-- Demo of nested DbContextScopes
+ _Output.WriteLine("Creating 2 new users called John and Jeanne in an atomic transaction...");
+ var johnSpec = new UserCreationSpec("John", "john@example.com");
+ var jeanneSpec = new UserCreationSpec("Jeanne", "jeanne@example.com");
+ userCreationService.CreateListOfUsers(johnSpec, jeanneSpec);
+ _Output.WriteLine("Done.\n");
+
+ _Output.WriteLine("Trying to retrieve our newly created users from the data store...");
+ var createdUsers = userQueryService.GetUsers(johnSpec.Id, jeanneSpec.Id).ToList();
+ _Output.WriteLine("OK. Found {0} persisted users.", createdUsers.Count);
+
+ Assert.Equal(2, createdUsers.Count);
+ johnSpec.Equal(createdUsers[0]);
+ jeanneSpec.Equal(createdUsers[1]);
+
+ //-- Demo of nested DbContextScopes in the face of an exception.
+ // If any of the provided users failed to get persisted, none should get persisted.
+ _Output.WriteLine(
+ "Creating 2 new users called Julie and Marc in an atomic transaction. Will make the persistence of the second user fail intentionally in order to test the atomicity of the transaction...");
+ var julieSpec = new UserCreationSpec("Julie", "julie@example.com");
+ var marcSpec = new UserCreationSpec("Marc", "marc@example.com");
+
+ Assert.ThrowsAny(() =>
+ {
+ userCreationService.CreateListOfUsersWithIntentionalFailure(julieSpec, marcSpec);
+ });
+
+ _Output.WriteLine("Trying to retrieve our newly created users from the data store...");
+ var maybeCreatedUsers = userQueryService.GetUsers(julieSpec.Id, marcSpec.Id).ToList();
+ _Output.WriteLine(
+ "Found {0} persisted users. If this number is 0, we're all good. If this number is not 0, we have a big problem.",
+ maybeCreatedUsers.Count);
+ Assert.Equal(0, maybeCreatedUsers.Count);
+
+ //-- Demo of DbContextScope within an async flow
+ _Output.WriteLine("Trying to retrieve two users John and Jeanne sequentially in an asynchronous manner...");
+ // We're going to block on the async task here as we don't have a choice. No risk of deadlocking in any case as console apps
+ // don't have a synchronization context.
+ var usersFoundAsync = userQueryService.GetTwoUsersAsync(johnSpec.Id, jeanneSpec.Id).Result;
+ _Output.WriteLine("OK. Found {0} persisted users.", usersFoundAsync.Count);
+ Assert.Equal(2, usersFoundAsync.Count);
+ johnSpec.Equal(usersFoundAsync[0]);
+ jeanneSpec.Equal(usersFoundAsync[1]);
+
+ //-- Demo of explicit database transaction.
+ _Output.WriteLine("Trying to retrieve user John within a READ UNCOMMITTED database transaction...");
+ // You'll want to use SQL Profiler or Entity Framework Profiler to verify that the correct transaction isolation
+ // level is being used.
+ var userMaybeUncommitted = userQueryService.GetUserUncommitted(johnSpec.Id);
+ _Output.WriteLine("OK. User found: {0}", userMaybeUncommitted);
+ johnSpec.Equal(userMaybeUncommitted);
+
+ //-- Demo of disabling the DbContextScope nesting behaviour in order to force the persistence of changes made to entities
+ // This is a pretty advanced feature that you can safely ignore until you actually need it.
+ _Output.WriteLine("Will simulate sending a Welcome email to John...");
+
+ using (var parentScope = dbContextScopeFactory.Create())
+ {
+ var parentDbContext = parentScope.DbContexts.Get();
+
+ // Load John in the parent DbContext
+ var john = parentDbContext.Users.Find(johnSpec.Id);
+ _Output.WriteLine("Before calling SendWelcomeEmail(), john.WelcomeEmailSent = " +
+ john.WelcomeEmailSent);
+
+ // Now call our SendWelcomeEmail() business logic service method, which will
+ // update John in a non-nested child context
+ userEmailService.SendWelcomeEmail(johnSpec.Id);
+
+ // Verify that we can see the modifications made to John by the SendWelcomeEmail() method
+ _Output.WriteLine("After calling SendWelcomeEmail(), john.WelcomeEmailSent = " +
+ john.WelcomeEmailSent);
+
+ // Note that even though we're not calling SaveChanges() in the parent scope here, the changes
+ // made to John by SendWelcomeEmail() will remain persisted in the database as SendWelcomeEmail()
+ // forced the creation of a new DbContextScope.
+ }
+
+ //-- Demonstration of DbContextScope and parallel programming
+ _Output.WriteLine(
+ "Calculating and storing the credit score of all users in the database in parallel...");
+ userCreditScoreService.UpdateCreditScoreForAllUsers();
+ _Output.WriteLine("Done.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DbContextScope.EF3.Test/DomainModel/User.cs b/src/DbContextScope.EF3.Test/DomainModel/User.cs
new file mode 100644
index 0000000..ed9e808
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/DomainModel/User.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace EntityFrameworkCore.DbContextScope.Test.DomainModel {
+ // Anemic model to keep this demo application simple.
+ public class User {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public string Email { get; set; }
+ public int CreditScore { get; set; }
+ public bool WelcomeEmailSent { get; set; }
+ public DateTime CreatedOn { get; set; }
+
+ public override string ToString() {
+ return String.Format("Id: {0} | Name: {1} | Email: {2} | CreditScore: {3} | WelcomeEmailSent: {4} | CreatedOn (UTC): {5}", Id, Name, Email, CreditScore, WelcomeEmailSent, CreatedOn.ToString("dd MMM yyyy - HH:mm:ss"));
+ }
+ }
+}
diff --git a/src/DbContextScope.EF3.Test/Repositories/IUserRepository.cs b/src/DbContextScope.EF3.Test/Repositories/IUserRepository.cs
new file mode 100644
index 0000000..6493fab
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/Repositories/IUserRepository.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading.Tasks;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+
+namespace EntityFrameworkCore.DbContextScope.Test.Repositories {
+ public interface IUserRepository {
+ User Get(Guid userId);
+ Task GetAsync(Guid userId);
+ void Add(User user);
+ }
+}
\ No newline at end of file
diff --git a/src/DbContextScope.EF3.Test/Repositories/UserRepository.cs b/src/DbContextScope.EF3.Test/Repositories/UserRepository.cs
new file mode 100644
index 0000000..fccc3af
--- /dev/null
+++ b/src/DbContextScope.EF3.Test/Repositories/UserRepository.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Threading.Tasks;
+using EntityFrameworkCore.DbContextScope.Test.DatabaseContext;
+using EntityFrameworkCore.DbContextScope.Test.DomainModel;
+
+namespace EntityFrameworkCore.DbContextScope.Test.Repositories {
+ /*
+ * An example "repository" relying on an ambient DbContext instance.
+ *
+ * Since we use EF to persist our data, the actual repository is of course the EF DbContext. This
+ * class is called a "repository" for old time's sake but is merely just a collection
+ * of pre-built Linq-to-Entities queries. This avoids having these queries copied and
+ * pasted in every service method that need them and facilitates unit testing.
+ *
+ * Whether your application would benefit from using this additional layer or would
+ * be better off if its service methods queried the DbContext directly or used some sort of query
+ * object pattern is a design decision for you to make.
+ *
+ * DbContextScope is agnostic to this and will happily let you use any approach you
+ * deem most suitable for your application.
+ *
+ */
+ public class UserRepository : IUserRepository {
+ private readonly IAmbientDbContextLocator _ambientDbContextLocator;
+
+ private UserManagementDbContext DbContext {
+ get {
+ var dbContext = _ambientDbContextLocator.Get();
+
+ if (dbContext == null)
+ throw new InvalidOperationException("No ambient DbContext of type UserManagementDbContext found. This means that this repository method has been called outside of the scope of a DbContextScope. A repository must only be accessed within the scope of a DbContextScope, which takes care of creating the DbContext instances that the repositories need and making them available as ambient contexts. This is what ensures that, for any given DbContext-derived type, the same instance is used throughout the duration of a business transaction. To fix this issue, use IDbContextScopeFactory in your top-level business logic service method to create a DbContextScope that wraps the entire business transaction that your service method implements. Then access this repository within that scope. Refer to the comments in the IDbContextScope.cs file for more details.");
+
+ return dbContext;
+ }
+ }
+
+ public UserRepository(IAmbientDbContextLocator ambientDbContextLocator) {
+ _ambientDbContextLocator = ambientDbContextLocator ?? throw new ArgumentNullException("ambientDbContextLocator");
+ }
+
+ public User Get(Guid userId) {
+ return DbContext.Users.Find(userId);
+ }
+
+ public Task GetAsync(Guid userId)
+ {
+ return DbContext.Users.FindAsync(userId).AsTask();
+
+//#if NETCOREAPP2_0 || NET461
+// return DbContext.Users.FindAsync(userId);
+//#endif
+//#if NETCOREAPP3_0
+// return DbContext.Users.FindAsync(userId).AsTask();
+//#endif
+ }
+
+ public void Add(User user) {
+ DbContext.Users.Add(user);
+ }
+ }
+}
diff --git a/DbContextScope.sln b/src/DbContextScope.sln
similarity index 56%
rename from DbContextScope.sln
rename to src/DbContextScope.sln
index 482ee3e..98a52e6 100644
--- a/DbContextScope.sln
+++ b/src/DbContextScope.sln
@@ -1,24 +1,21 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26730.16
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29806.167
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{B9148EB1-044F-442C-AC23-B6ADD1E3FB2E}"
- ProjectSection(SolutionItems) = preProject
- .nuget\NuGet.Config = .nuget\NuGet.Config
- .nuget\NuGet.exe = .nuget\NuGet.exe
- .nuget\NuGet.targets = .nuget\NuGet.targets
- EndProjectSection
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9BE61ABE-754C-4880-9B6D-0187E33BAB09}"
ProjectSection(SolutionItems) = preProject
+ ..\Directory.build.props = ..\Directory.build.props
+ ..\GitVersion.yml = ..\GitVersion.yml
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DbContextScope", "DbContextScope\DbContextScope.csproj", "{D84F0F17-48F7-4BF6-B679-07F57A23CEA6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{BB1A9648-FEF6-4A80-B7EC-C0C8B69698FB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DbContextScope.EF2.Test", "DbContextScope.EF2.Test\DbContextScope.EF2.Test.csproj", "{A0EAD0E3-563E-4DBA-B372-6184F621D83A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DbContextScope.EF3.Test", "DbContextScope.EF3.Test\DbContextScope.EF3.Test.csproj", "{CDDBFD34-CE32-419F-B778-C54EFACA5B6C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -30,10 +27,14 @@ Global
{D84F0F17-48F7-4BF6-B679-07F57A23CEA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D84F0F17-48F7-4BF6-B679-07F57A23CEA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D84F0F17-48F7-4BF6-B679-07F57A23CEA6}.Release|Any CPU.Build.0 = Release|Any CPU
- {BB1A9648-FEF6-4A80-B7EC-C0C8B69698FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BB1A9648-FEF6-4A80-B7EC-C0C8B69698FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BB1A9648-FEF6-4A80-B7EC-C0C8B69698FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BB1A9648-FEF6-4A80-B7EC-C0C8B69698FB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A0EAD0E3-563E-4DBA-B372-6184F621D83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A0EAD0E3-563E-4DBA-B372-6184F621D83A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A0EAD0E3-563E-4DBA-B372-6184F621D83A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A0EAD0E3-563E-4DBA-B372-6184F621D83A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CDDBFD34-CE32-419F-B778-C54EFACA5B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CDDBFD34-CE32-419F-B778-C54EFACA5B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CDDBFD34-CE32-419F-B778-C54EFACA5B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CDDBFD34-CE32-419F-B778-C54EFACA5B6C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/DbContextScope/CallContext.cs b/src/DbContextScope/CallContext.cs
new file mode 100644
index 0000000..24d321d
--- /dev/null
+++ b/src/DbContextScope/CallContext.cs
@@ -0,0 +1,32 @@
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace EntityFrameworkCore.DbContextScope
+{
+
+ ///
+ /// Provides a way to set contextual data that flows with the call and
+ /// async context of a test or invocation.
+ /// http://www.cazzulino.com/callcontext-netstandard-netcore.html
+ ///
+ internal static class CallContext
+ {
+ static ConcurrentDictionary> state = new ConcurrentDictionary>();
+
+ ///
+ /// Stores a given object and associates it with the specified name.
+ ///
+ /// The name with which to associate the new item in the call context.
+ /// The object to store in the call context.
+ public static void SetData(string name, object data) =>
+ state.GetOrAdd(name, _ => new AsyncLocal