diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a199e6a..f1635bb 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "csharpier": { - "version": "0.25.0", + "version": "0.26.2", "commands": [ "dotnet-csharpier" ] diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bd26b80..13a5847 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -61,7 +61,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: '8.0.x' - dotnet-quality: 'preview' + dotnet-quality: 'ga' # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 2cfe7f6..b8f6b2e 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -16,7 +16,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: '8.0.x' - dotnet-quality: 'preview' + dotnet-quality: 'ga' - name: Restore dependencies run: dotnet restore - name: Build @@ -36,7 +36,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: '8.0.x' - dotnet-quality: 'preview' + dotnet-quality: 'ga' - name: Restore dependencies run: dotnet restore - name: Build NuGet diff --git a/.gitignore b/.gitignore index 77404d7..706bb44 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ data/ msbuild.log msbuild.err msbuild.wrn + +.idea/ diff --git a/DbContextScope.Tests/DbContextScope.Tests.csproj b/DbContextScope.Tests/DbContextScope.Tests.csproj index 29e80d8..e514988 100644 --- a/DbContextScope.Tests/DbContextScope.Tests.csproj +++ b/DbContextScope.Tests/DbContextScope.Tests.csproj @@ -8,11 +8,11 @@ - - + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/DbContextScope.Tests/DbContextScopeTests.cs b/DbContextScope.Tests/DbContextScopeTests.cs index 9da7638..072422b 100644 --- a/DbContextScope.Tests/DbContextScopeTests.cs +++ b/DbContextScope.Tests/DbContextScopeTests.cs @@ -1,3 +1,4 @@ +using DbContextScope.Exceptions; using FluentAssertions; using Microsoft.EntityFrameworkCore; using System; @@ -59,11 +60,24 @@ public void Nested_scopes_should_use_same_DbContext_by_default() } } + [Fact] + public void Calling_GetRequired_on_an_AmbientDbContextLocator_outside_of_an_ambient_scope_should_throw() + { + var ex = Record.Exception(() => + { + var contextLocator = new AmbientDbContextLocator(); + contextLocator.GetRequired(); + }); + + ex.Should().NotBeNull(); + ex.Should().BeOfType(); + } + [Fact] public void Calling_SaveChanges_on_a_nested_scope_has_no_effect() { - var originalName = "Test User"; - var newName = "New name"; + const string originalName = "Test User"; + const string newName = "New name"; // Arrange - add one user to the database using (var dbContext = _dbContextFactory.CreateDbContext()) @@ -95,8 +109,8 @@ public void Calling_SaveChanges_on_a_nested_scope_has_no_effect() [Fact] public void Calling_SaveChanges_on_the_outer_scope_saves_changes() { - var originalName = "Test User"; - var newName = "New name"; + const string originalName = "Test User"; + const string newName = "New name"; // Arrange - add one user to the database using (var dbContext = _dbContextFactory.CreateDbContext()) @@ -134,7 +148,10 @@ public void Changes_can_only_be_saved_once_on_a_DbContextScope() dbContextScope.SaveChanges(); // Act - call SaveChanges again - var ex = Record.Exception(() => dbContextScope.SaveChanges()); + var ex = Record.Exception(() => + { + dbContextScope.SaveChanges(); + }); // Assert - an InvalidOperationException should have been thrown ex.Should().NotBeNull(); @@ -145,9 +162,9 @@ public void Changes_can_only_be_saved_once_on_a_DbContextScope() [Fact] public void SaveChanges_can_be_called_again_after_a_DbUpdateConcurrencyException() { - var originalName = "Test User"; - var newName1 = "New name 1"; - var newName2 = "New name 2"; + const string originalName = "Test User"; + const string newName1 = "New name 1"; + const string newName2 = "New name 2"; // Arrange - add one user to the database using (var dbContext = _dbContextFactory.CreateDbContext()) @@ -228,9 +245,7 @@ public void IDbContextReadOnlyScope_should_not_have_SaveChanges_method() var publicMethods = type.GetMethods( BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly ); - var saveChangesMethod = publicMethods - .Where(m => m.Name == "SaveChanges") - .SingleOrDefault(); + var saveChangesMethod = publicMethods.SingleOrDefault(m => m.Name == "SaveChanges"); saveChangesMethod.Should().BeNull(); } } @@ -276,20 +291,17 @@ public void ForceCreateNew_option_should_create_new_DbContext_in_nested_scope() [Fact] public void RefreshEntitiesInParentScope_should_reload_changed_data_from_database() { - var originalName1 = "Test User 1"; - var originalName2 = "Test User 2"; - var newName1 = "New name 1"; - var newName2 = "New name 2"; + const string originalName1 = "Test User 1"; + const string originalName2 = "Test User 2"; + const string newName1 = "New name 1"; + const string newName2 = "New name 2"; // Arrange - add two users to the database using (var dbContext = _dbContextFactory.CreateDbContext()) { dbContext.Users.AddRange( - new User[] - { - new User { Name = originalName1 }, - new User { Name = originalName2 } - } + new User { Name = originalName1 }, + new User { Name = originalName2 } ); dbContext.SaveChanges(); } @@ -340,24 +352,21 @@ public void RefreshEntitiesInParentScope_should_refresh_entities_with_composite_ using (var dbContext = _dbContextFactory.CreateDbContext()) { dbContext.Users.AddRange( - new User[] + new() { - new User + Name = "Test User 1", + CoursesUsers = new CourseUser[] { - Name = "Test User 1", - CoursesUsers = new CourseUser[] - { - new CourseUser { Course = course1, Grade = "A" }, - new CourseUser { Course = course2, Grade = "C" } - } - }, - new User + new() { Course = course1, Grade = "A" }, + new() { Course = course2, Grade = "C" } + } + }, + new() + { + Name = "Test User 2", + CoursesUsers = new CourseUser[] { - Name = "Test User 2", - CoursesUsers = new CourseUser[] - { - new CourseUser { Course = course1, Grade = "F" } - } + new() { Course = course1, Grade = "F" } } } ); diff --git a/DbContextScope.Tests/Helpers/SqliteMemoryDatabaseLifetimeManager.cs b/DbContextScope.Tests/Helpers/SqliteMemoryDatabaseLifetimeManager.cs index 46ecb9d..62f563a 100644 --- a/DbContextScope.Tests/Helpers/SqliteMemoryDatabaseLifetimeManager.cs +++ b/DbContextScope.Tests/Helpers/SqliteMemoryDatabaseLifetimeManager.cs @@ -4,7 +4,18 @@ namespace Zejji.Tests.Helpers { - internal class SqliteMemoryDatabaseLifetimeManager : IDisposable + /// + /// This class is responsible for keeping an in-memory SQLite database + /// alive for the lifetime of an instance. This is required because SQLite + /// in-memory databases cease to exist as soon as the last database + /// connection is closed. + /// + /// See this link for more information. + /// + /// An instance of this class instantiates a SQLite database upon creation + /// and disposes of it when it is itself disposed. + /// + internal sealed class SqliteMemoryDatabaseLifetimeManager : IDisposable { public readonly string ConnectionString = $"DataSource={Guid.NewGuid()};mode=memory;cache=shared"; @@ -23,13 +34,13 @@ public void Dispose() // see https://rules.sonarsource.com/csharp/RSPEC-3881 GC.SuppressFinalize(this); } - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { - if (_keepAliveConnection != null) - { - _keepAliveConnection.Dispose(); - _keepAliveConnection = null; - } + if (_keepAliveConnection == null) + return; + + _keepAliveConnection.Dispose(); + _keepAliveConnection = null; } } } diff --git a/DbContextScope.sln b/DbContextScope.sln index 46e0edd..6c1fbb1 100644 --- a/DbContextScope.sln +++ b/DbContextScope.sln @@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution .gitattributes = .gitattributes .gitignore = .gitignore Directory.Build.props = Directory.Build.props + .config\dotnet-tools.json = .config\dotnet-tools.json global.json = global.json README.md = README.md stylecop.json = stylecop.json diff --git a/DbContextScope/DbContextScope.csproj b/DbContextScope/DbContextScope.csproj index 3b813ad..4915fe6 100644 --- a/DbContextScope/DbContextScope.csproj +++ b/DbContextScope/DbContextScope.csproj @@ -4,7 +4,7 @@ net8.0 enable Zejji.DbContextScope.EFCore - 8.0.100-rc.2 + 8.0.100.1 Mehdi El Gueddari;Gerard Howell DbContextScope;DbContext;EF;EFCore;EntityFramework;EntityFrameworkCore;UnitOfWork;AmbientDbContext;AmbientContext;RepositoryPattern A library for managing the lifetime of Entity Framework Core DbContext instances. This package is based on the original DbContextScope repository by Mehdi El Gueddari, updated for .NET 6+ and EF Core, with a number of additional improvements and bug fixes. @@ -22,9 +22,9 @@ - - - + + + diff --git a/DbContextScope/Enums/DbContextScopeOption.cs b/DbContextScope/Enums/DbContextScopeOption.cs index 9c5a26f..151d2ca 100644 --- a/DbContextScope/Enums/DbContextScopeOption.cs +++ b/DbContextScope/Enums/DbContextScopeOption.cs @@ -1,16 +1,18 @@ -namespace Zejji.Entity +using Microsoft.EntityFrameworkCore; + +namespace Zejji.Entity { /// - /// Indicates whether or not a new DbContextScope will join the ambient scope. + /// Indicates whether or not a new will join the ambient scope. /// public enum DbContextScopeOption { /// - /// Join the ambient DbContextScope if one exists. Creates a new + /// Join the ambient if one exists. Creates a new /// one otherwise. /// /// This is what you want in most cases. Joining the existing ambient scope - /// ensures that all code within a business transaction uses the same DbContext + /// ensures that all code within a business transaction uses the same /// instance and that all changes made by service methods called within that /// business transaction are either committed or rolled back atomically when the top-level /// scope completes (i.e. it ensures that there are no partial commits). @@ -18,17 +20,17 @@ public enum DbContextScopeOption JoinExisting, /// - /// Ignore the ambient DbContextScope (if any) and force the creation of - /// a new DbContextScope. + /// Ignore the ambient (if any) and force the creation of + /// a new . /// /// This is an advanced feature that should be used with great care. /// - /// When forcing the creation of a new scope, new DbContext instances will be - /// created within that inner scope instead of re-using the DbContext instances that + /// When forcing the creation of a new scope, new instances will be + /// created within that inner scope instead of re-using the instances that /// the parent scope (if any) is using. /// /// Any changes made to entities within that inner scope will therefore get persisted - /// to the database when SaveChanges() is called in the inner scope regardless of whether + /// to the database when is called in the inner scope regardless of whether /// or not the parent scope is successful. /// /// You would typically do this to ensure that the changes made within the inner scope diff --git a/DbContextScope/Exceptions/NoAmbientDbContextScopeException.cs b/DbContextScope/Exceptions/NoAmbientDbContextScopeException.cs new file mode 100644 index 0000000..99c5bc9 --- /dev/null +++ b/DbContextScope/Exceptions/NoAmbientDbContextScopeException.cs @@ -0,0 +1,18 @@ +namespace DbContextScope.Exceptions; + +using System; + +/// +/// A custom exception that is thrown when an operation which is required to be run in the presence +/// of an ambient is run with no such scope present. +/// +public class NoAmbientDbContextScopeException : Exception +{ + public NoAmbientDbContextScopeException() { } + + public NoAmbientDbContextScopeException(string message) + : base(message) { } + + public NoAmbientDbContextScopeException(string message, Exception inner) + : base(message, inner) { } +} diff --git a/DbContextScope/Implementations/AmbientDbContextLocator.cs b/DbContextScope/Implementations/AmbientDbContextLocator.cs index aa6025c..c26ea81 100644 --- a/DbContextScope/Implementations/AmbientDbContextLocator.cs +++ b/DbContextScope/Implementations/AmbientDbContextLocator.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using DbContextScope.Exceptions; +using Microsoft.EntityFrameworkCore; namespace Zejji.Entity { @@ -10,5 +11,17 @@ public class AmbientDbContextLocator : IAmbientDbContextLocator var ambientDbContextScope = DbContextScope.GetAmbientScope(); return ambientDbContextScope?.DbContexts.Get(); } + + public TDbContext GetRequired() + where TDbContext : DbContext + { + var ambientDbContextScope = + DbContextScope.GetAmbientScope() + ?? throw new NoAmbientDbContextScopeException( + $"No ambient {nameof(DbContext)} of type {typeof(TDbContext).Name} found. This usually means that this repository method has been called outside of the scope of a {nameof(DbContextScope)}. A repository must only be accessed within the scope of a {nameof(DbContextScope)}, which takes care of creating the {nameof(DbContext)} instances that the repositories need and making them available as ambient contexts. This is what ensures that, for any given {nameof(DbContext)}-derived type, the same instance is used throughout the duration of a business transaction. To fix this issue, use {nameof(IDbContextScopeFactory)} in your top-level business logic service method to create a {nameof(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 {nameof(IDbContextScope)}.cs file for more details." + ); + + return ambientDbContextScope.DbContexts.Get(); + } } } diff --git a/DbContextScope/Implementations/DbContextCollection.cs b/DbContextScope/Implementations/DbContextCollection.cs index c5f1654..9142c56 100644 --- a/DbContextScope/Implementations/DbContextCollection.cs +++ b/DbContextScope/Implementations/DbContextCollection.cs @@ -10,48 +10,29 @@ namespace Zejji.Entity { /// - /// As its name suggests, DbContextCollection maintains a collection of DbContext instances. + /// As its name suggests, maintains a collection of instances. /// /// What it does in a nutshell: - /// - Lazily instantiates DbContext instances when its Get Of TDbContext () method is called + /// - Lazily instantiates instances when its method is called /// (and optionally starts an explicit database transaction). - /// - Keeps track of the DbContext instances it created so that it can return the existing - /// instance when asked for a DbContext of a specific type. - /// - Takes care of committing / rolling back changes and transactions on all the DbContext - /// instances it created when its Commit() or Rollback() method is called. + /// - Keeps track of the instances it created so that it can return the existing + /// instance when asked for a of a specific type. + /// - Takes care of committing / rolling back changes and transactions on all the + /// instances it created when its or method is called. /// /// - public class DbContextCollection : IDbContextCollection + public class DbContextCollection( + bool readOnly = false, + IsolationLevel? isolationLevel = null, + IDbContextFactory? dbContextFactory = null + ) : IDbContextCollection { - private Dictionary _initializedDbContexts; - private Dictionary _transactions; - private readonly IsolationLevel? _isolationLevel; - private readonly IDbContextFactory? _dbContextFactory; - private bool _disposed; - private bool _completed; - private readonly bool _readOnly; - - internal Dictionary InitializedDbContexts - { - get { return _initializedDbContexts; } - } + private readonly Dictionary _initializedDbContexts = new(); + private readonly Dictionary _transactions = new(); + private bool _disposed = false; + private bool _completed = false; - public DbContextCollection( - bool readOnly = false, - IsolationLevel? isolationLevel = null, - IDbContextFactory? dbContextFactory = null - ) - { - _disposed = false; - _completed = false; - - _initializedDbContexts = new Dictionary(); - _transactions = new Dictionary(); - - _readOnly = readOnly; - _isolationLevel = isolationLevel; - _dbContextFactory = dbContextFactory; - } + internal Dictionary InitializedDbContexts => _initializedDbContexts; public TDbContext Get() where TDbContext : DbContext @@ -66,20 +47,20 @@ public TDbContext Get() // First time we've been asked for this particular DbContext type. // Create one, cache it and start its database transaction if needed. TDbContext dbContext = - _dbContextFactory != null - ? _dbContextFactory.CreateDbContext() + dbContextFactory != null + ? dbContextFactory.CreateDbContext() : Activator.CreateInstance(); _initializedDbContexts.Add(requestedType, dbContext); - if (_readOnly) + if (readOnly) { dbContext.ChangeTracker.AutoDetectChangesEnabled = false; } - if (_isolationLevel.HasValue) + if (isolationLevel.HasValue) { - var tran = dbContext.Database.BeginTransaction(_isolationLevel.Value); + var tran = dbContext.Database.BeginTransaction(isolationLevel.Value); _transactions.Add(dbContext, tran); } } @@ -120,7 +101,7 @@ public int Commit() { try { - if (!_readOnly) + if (!readOnly) { c += dbContext.SaveChanges(); } @@ -171,7 +152,7 @@ public async Task CommitAsync(CancellationToken cancelToken) { try { - if (!_readOnly) + if (!readOnly) { c += await dbContext.SaveChangesAsync(cancelToken).ConfigureAwait(false); } @@ -253,7 +234,7 @@ public void Dispose() { try { - if (_readOnly) + if (readOnly) Commit(); else Rollback(); @@ -282,12 +263,15 @@ public void Dispose() /// /// Returns the value associated with the specified key or the default - /// value for the TValue type. + /// value for the type. /// + /// The type of the lookup key. + /// The type of the value stored in the dictionary. private static TValue? GetValueOrDefault( - IDictionary dictionary, + Dictionary dictionary, TKey key ) + where TKey : notnull { return dictionary.TryGetValue(key, out var value) ? value : default; } diff --git a/DbContextScope/Implementations/DbContextReadOnlyScope.cs b/DbContextScope/Implementations/DbContextReadOnlyScope.cs index 9dcaa58..b31cfe9 100644 --- a/DbContextScope/Implementations/DbContextReadOnlyScope.cs +++ b/DbContextScope/Implementations/DbContextReadOnlyScope.cs @@ -2,9 +2,19 @@ namespace Zejji.Entity { - public class DbContextReadOnlyScope : IDbContextReadOnlyScope + public class DbContextReadOnlyScope( + DbContextScopeOption joiningOption, + IsolationLevel? isolationLevel, + IDbContextFactory? dbContextFactory = null + ) : IDbContextReadOnlyScope { - private DbContextScope _internalScope; + private readonly DbContextScope _internalScope = + new( + joiningOption: joiningOption, + readOnly: true, + isolationLevel: isolationLevel, + dbContextFactory: dbContextFactory + ); public IDbContextCollection DbContexts => _internalScope.DbContexts; @@ -25,20 +35,6 @@ public DbContextReadOnlyScope( dbContextFactory: dbContextFactory ) { } - public DbContextReadOnlyScope( - DbContextScopeOption joiningOption, - IsolationLevel? isolationLevel, - IDbContextFactory? dbContextFactory = null - ) - { - _internalScope = new DbContextScope( - joiningOption: joiningOption, - readOnly: true, - isolationLevel: isolationLevel, - dbContextFactory: dbContextFactory - ); - } - public void Dispose() { _internalScope.Dispose(); diff --git a/DbContextScope/Implementations/FuncDictionaryByType.cs b/DbContextScope/Implementations/FuncDictionaryByType.cs index a5f9013..48b7ff0 100644 --- a/DbContextScope/Implementations/FuncDictionaryByType.cs +++ b/DbContextScope/Implementations/FuncDictionaryByType.cs @@ -14,7 +14,7 @@ internal class FuncDictionaryByType /// /// Maps the specified type argument to the given function. If /// the type argument already has a value within the dictionary, - /// ArgumentException is thrown. + /// is thrown. /// public void Add(Func func) { @@ -23,7 +23,7 @@ public void Add(Func func) /// /// Attempts to fetch a function from the dictionary, returning false and - /// setting the output parameter to the default value for Func<T> if it + /// setting the output parameter to the default value for if it /// fails, or returning true and setting the output parameter to the /// fetched function if it succeeds. /// diff --git a/DbContextScope/Implementations/RegisteredDbContextFactory.cs b/DbContextScope/Implementations/RegisteredDbContextFactory.cs index e295c02..07bd9cf 100644 --- a/DbContextScope/Implementations/RegisteredDbContextFactory.cs +++ b/DbContextScope/Implementations/RegisteredDbContextFactory.cs @@ -4,7 +4,7 @@ namespace Zejji.Entity { /// - /// A factory to create DbContext instances. + /// A factory to create instances. /// /// /// It should be registered in a dependency injection container as a singleton. @@ -35,7 +35,7 @@ public TDbContext CreateDbContext() if (!isTypeRegistered) { throw new InvalidOperationException( - $"{typeof(TDbContext).Name} was not registered with the {typeof(RegisteredDbContextFactory).Name} instance. Make sure you call {nameof(RegisterDbContextType)}." + $"{typeof(TDbContext).Name} was not registered with the {nameof(RegisteredDbContextFactory)} instance. Make sure you call {nameof(RegisterDbContextType)}." ); } diff --git a/DbContextScope/Implementations/ServiceProviderDbContextFactory.cs b/DbContextScope/Implementations/ServiceProviderDbContextFactory.cs new file mode 100644 index 0000000..049d782 --- /dev/null +++ b/DbContextScope/Implementations/ServiceProviderDbContextFactory.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Zejji.Entity; + +/// +/// A factory to create instances +/// using an instance. +/// +/// +/// It should be registered in a dependency injection container as a singleton. +/// +public class ServiceProviderDbContextFactory(IServiceProvider serviceProvider) + : IDbContextFactory +{ + public TDbContext CreateDbContext() + where TDbContext : DbContext + { + return (TDbContext)ActivatorUtilities.CreateInstance(serviceProvider, typeof(TDbContext)); + } +} diff --git a/DbContextScope/Interfaces/IAmbientDbContextLocator.cs b/DbContextScope/Interfaces/IAmbientDbContextLocator.cs index 37d3634..d5a024c 100644 --- a/DbContextScope/Interfaces/IAmbientDbContextLocator.cs +++ b/DbContextScope/Interfaces/IAmbientDbContextLocator.cs @@ -1,19 +1,30 @@ -using Microsoft.EntityFrameworkCore; +using DbContextScope.Exceptions; +using Microsoft.EntityFrameworkCore; namespace Zejji.Entity { /// - /// Convenience methods to retrieve ambient DbContext instances. + /// Convenience methods to retrieve ambient instances. /// public interface IAmbientDbContextLocator { /// - /// If called within the scope of a DbContextScope, gets or creates - /// the ambient DbContext instance for the provided DbContext type. + /// If called within the scope of a , gets or creates + /// the ambient instance for the provided type. /// /// Otherwise returns null. /// TDbContext? Get() where TDbContext : DbContext; + + /// + /// If called within the scope of a , gets or creates + /// the ambient instance for the provided type. + /// + /// Otherwise throws a . + /// + /// Thrown when there is no ambient . + TDbContext GetRequired() + where TDbContext : DbContext; } } diff --git a/DbContextScope/Interfaces/IDbContextCollection.cs b/DbContextScope/Interfaces/IDbContextCollection.cs index d61a438..7e584ae 100644 --- a/DbContextScope/Interfaces/IDbContextCollection.cs +++ b/DbContextScope/Interfaces/IDbContextCollection.cs @@ -4,12 +4,12 @@ namespace Zejji.Entity { /// - /// Maintains a list of lazily-created DbContext instances. + /// Maintains a list of lazily-created instances. /// public interface IDbContextCollection : IDisposable { /// - /// Get or create a DbContext instance of the specified type. + /// Get or create a instance of the specified type. /// TDbContext Get() where TDbContext : DbContext; diff --git a/DbContextScope/Interfaces/IDbContextFactory.cs b/DbContextScope/Interfaces/IDbContextFactory.cs index fbb4863..c53de34 100644 --- a/DbContextScope/Interfaces/IDbContextFactory.cs +++ b/DbContextScope/Interfaces/IDbContextFactory.cs @@ -3,26 +3,26 @@ namespace Zejji.Entity { /// - /// Factory for DbContext-derived classes that don't expose + /// Factory for -derived classes that don't expose /// a default constructor. /// /// - /// If your DbContext-derived classes have a default constructor, - /// you can ignore this factory. DbContextScope will take care of - /// instantiating your DbContext class with Activator.CreateInstance() + /// If your -derived classes have a default constructor, + /// you can ignore this factory. will take care of + /// instantiating your class with /// when needed. /// - /// If your DbContext-derived classes don't expose a default constructor - /// however, you must implement this interface and provide it to DbContextScope - /// so that it can create instances of your DbContexts. + /// If your -derived classes don't expose a default constructor + /// however, you must implement this interface and provide it to + /// so that it can create instances of your s. /// - /// A typical situation where this would be needed is in the case of your DbContext-derived + /// A typical situation where this would be needed is in the case of your -derived /// class having a dependency on some other component in your application. For example, - /// some data in your database may be encrypted and you might want your DbContext-derived + /// some data in your database may be encrypted and you might want your -derived /// class to automatically decrypt this data on entity materialization. It would therefore /// have a mandatory dependency on an IDataDecryptor component that knows how to do that. - /// In that case, you'll want to implement this interface and pass it to the DbContextScope - /// you're creating so that DbContextScope is able to create your DbContext instances correctly. + /// In that case, you'll want to implement this interface and pass it to the + /// you're creating so that is able to create your instances correctly. /// public interface IDbContextFactory { diff --git a/DbContextScope/Interfaces/IDbContextReadOnlyScope.cs b/DbContextScope/Interfaces/IDbContextReadOnlyScope.cs index d0cdbf3..fd4b526 100644 --- a/DbContextScope/Interfaces/IDbContextReadOnlyScope.cs +++ b/DbContextScope/Interfaces/IDbContextReadOnlyScope.cs @@ -1,15 +1,16 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; namespace Zejji.Entity { /// - /// A read-only DbContextScope. Refer to the comments for IDbContextScope + /// A read-only . Refer to the comments for /// for more details. /// public interface IDbContextReadOnlyScope : IDisposable { /// - /// The DbContext instances that this DbContextScope manages. + /// The instances that this manages. /// IDbContextCollection DbContexts { get; } } diff --git a/DbContextScope/Interfaces/IDbContextScope.cs b/DbContextScope/Interfaces/IDbContextScope.cs index f0a629d..75b4c5c 100644 --- a/DbContextScope/Interfaces/IDbContextScope.cs +++ b/DbContextScope/Interfaces/IDbContextScope.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; using System.Collections; using System.Threading; using System.Threading.Tasks; @@ -6,61 +7,61 @@ namespace Zejji.Entity { /// - /// Creates and manages the DbContext instances used by this code block. + /// Creates and manages the instances used by this code block. /// - /// You typically use a DbContextScope at the business logic service level. Each + /// You typically use a at the business logic service level. Each /// business transaction (i.e. each service method) that uses Entity Framework must - /// be wrapped in a DbContextScope, ensuring that the same DbContext instances + /// be wrapped in a , ensuring that the same DbContext instances /// are used throughout the business transaction and are committed or rolled /// back atomically. /// - /// Think of it as TransactionScope but for managing DbContext instances instead - /// of database transactions. Just like a TransactionScope, a DbContextScope is + /// Think of it as TransactionScope but for managing instances instead + /// of database transactions. Just like a TransactionScope, a is /// ambient, can be nested and supports async execution flows. /// /// And just like TransactionScope, it does not support parallel execution flows. - /// You therefore MUST suppress the ambient DbContextScope before kicking off parallel - /// tasks or you will end up with multiple threads attempting to use the same DbContext - /// instances (use IDbContextScopeFactory.SuppressAmbientContext() for this). + /// You therefore MUST suppress the ambient before kicking off parallel + /// tasks or you will end up with multiple threads attempting to use the same + /// instances (use for this). /// - /// You can access the DbContext instances that this scopes manages via either: - /// - its DbContexts property, or - /// - an IAmbientDbContextLocator + /// You can access the instances that this scopes manages via either: + /// - its property, or + /// - an /// /// (you would typically use the later in the repository / query layer to allow your repository - /// or query classes to access the ambient DbContext instances without giving them access to the actual - /// DbContextScope). + /// or query classes to access the ambient instances without giving them access to the actual + /// ). /// /// public interface IDbContextScope : IDisposable { /// - /// Saves the changes in all the DbContext instances that were created within this scope. + /// Saves the changes in all the instances that were created within this scope. /// This method can only be called once per scope. /// int SaveChanges(); /// - /// Saves the changes in all the DbContext instances that were created within this scope. + /// Saves the changes in all the instances that were created within this scope. /// This method can only be called once per scope. /// Task SaveChangesAsync(); /// - /// Saves the changes in all the DbContext instances that were created within this scope. + /// Saves the changes in all the instances that were created within this scope. /// This method can only be called once per scope. /// Task SaveChangesAsync(CancellationToken cancelToken); /// /// Reloads the provided persistent entities from the data store - /// in the DbContext instances managed by the parent scope. + /// in the instances managed by the parent scope. /// - /// If there is no parent scope (i.e. if this DbContextScope + /// If there is no parent scope (i.e. if this /// is the top-level scope), does nothing. /// /// This is useful when you have forced the creation of a new - /// DbContextScope and want to make sure that the parent scope + /// and want to make sure that the parent scope /// (if any) is aware of the entities you've modified in the /// inner scope. /// @@ -71,13 +72,13 @@ public interface IDbContextScope : IDisposable /// /// Reloads the provided persistent entities from the data store - /// in the DbContext instances managed by the parent scope. + /// in the instances managed by the parent scope. /// - /// If there is no parent scope (i.e. if this DbContextScope + /// If there is no parent scope (i.e. if this /// is the top-level scope), does nothing. /// /// This is useful when you have forced the creation of a new - /// DbContextScope and want to make sure that the parent scope + /// and want to make sure that the parent scope /// (if any) is aware of the entities you've modified in the /// inner scope. /// @@ -87,7 +88,8 @@ public interface IDbContextScope : IDisposable Task RefreshEntitiesInParentScopeAsync(IEnumerable entities); /// - /// The DbContext instances that this DbContextScope manages. Don't call SaveChanges() on the DbContext themselves! + /// The instances that this manages. + /// Don't call on the themselves! /// Save the scope instead. /// IDbContextCollection DbContexts { get; } diff --git a/DbContextScope/Interfaces/IDbContextScopeFactory.cs b/DbContextScope/Interfaces/IDbContextScopeFactory.cs index b240c4f..cf2b21b 100644 --- a/DbContextScope/Interfaces/IDbContextScopeFactory.cs +++ b/DbContextScope/Interfaces/IDbContextScopeFactory.cs @@ -1,22 +1,23 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; using System.Data; namespace Zejji.Entity { /// - /// Convenience methods to create a new ambient DbContextScope. This is the prefered method - /// to create a DbContextScope. + /// Convenience methods to create a new ambient . This is the preferred method + /// to create a . /// public interface IDbContextScopeFactory { /// - /// Creates a new DbContextScope. + /// Creates a new . /// /// By default, the new scope will join the existing ambient scope. This - /// is what you want in most cases. This ensures that the same DbContext instances + /// is what you want in most cases. This ensures that the same instances /// are used by all services methods called within the scope of a business transaction. /// - /// Set 'joiningOption' to 'ForceCreateNew' if you want to ignore the ambient scope + /// Set '' to '' if you want to ignore the ambient scope /// and force the creation of new DbContext instances within that scope. Using 'ForceCreateNew' /// is an advanced feature that should be used with great care and only if you fully understand the /// implications of doing this. @@ -26,13 +27,13 @@ IDbContextScope Create( ); /// - /// Creates a new DbContextScope for read-only queries. + /// Creates a new for read-only queries. /// /// By default, the new scope will join the existing ambient scope. This - /// is what you want in most cases. This ensures that the same DbContext instances + /// is what you want in most cases. This ensures that the same instances /// are used by all services methods called within the scope of a business transaction. /// - /// Set 'joiningOption' to 'ForceCreateNew' if you want to ignore the ambient scope + /// Set '' to '' if you want to ignore the ambient scope /// and force the creation of new DbContext instances within that scope. Using 'ForceCreateNew' /// is an advanced feature that should be used with great care and only if you fully understand the /// implications of doing this. @@ -42,8 +43,8 @@ IDbContextReadOnlyScope CreateReadOnly( ); /// - /// Forces the creation of a new ambient DbContextScope (i.e. does not - /// join the ambient scope if there is one) and wraps all DbContext instances + /// Forces the creation of a new ambient (i.e. does not + /// join the ambient scope if there is one) and wraps all instances /// created within that scope in an explicit database transaction with /// the provided isolation level. /// @@ -58,8 +59,8 @@ IDbContextReadOnlyScope CreateReadOnly( IDbContextScope CreateWithTransaction(IsolationLevel isolationLevel); /// - /// Forces the creation of a new ambient read-only DbContextScope (i.e. does not - /// join the ambient scope if there is one) and wraps all DbContext instances + /// Forces the creation of a new ambient read-only (i.e. does not + /// join the ambient scope if there is one) and wraps all instances /// created within that scope in an explicit database transaction with /// the provided isolation level. /// @@ -74,13 +75,13 @@ IDbContextReadOnlyScope CreateReadOnly( IDbContextReadOnlyScope CreateReadOnlyWithTransaction(IsolationLevel isolationLevel); /// - /// Temporarily suppresses the ambient DbContextScope. + /// Temporarily suppresses the ambient . /// - /// Always use this if you need to kick off parallel tasks within a DbContextScope. + /// Always use this if you need to kick off parallel tasks within a . /// This will prevent the parallel tasks from using the current ambient scope. If you - /// were to kick off parallel tasks within a DbContextScope without suppressing the ambient - /// context first, all the parallel tasks would end up using the same ambient DbContextScope, which - /// would result in multiple threads accesssing the same DbContext instances at the same + /// were to kick off parallel tasks within a without suppressing the ambient + /// context first, all the parallel tasks would end up using the same ambient , which + /// would result in multiple threads accessing the same instances at the same /// time. /// IDisposable SuppressAmbientContext(); diff --git a/global.json b/global.json index 6b9d0cf..f3365c4 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.100-rc.2" + "version": "8.0.100" } } \ No newline at end of file