public AmbientContextSuppressor()
        {
            this.savedScope = DbContextScope.GetAmbientScope();

            // We're hiding the ambient scope but not removing its instance
            // altogether. This is to be tolerant to some programming errors. 
            // 
            // Suppose we removed the ambient scope instance here. If someone
            // was to start a parallel task without suppressing
            // the ambient context and then tried to suppress the ambient
            // context within the parallel task while the original flow
            // of execution was still ongoing (a strange thing to do, I know,
            // but I'm sure this is going to happen), we would end up 
            // removing the ambient context instance of the original flow 
            // of execution from within the parallel flow of execution!
            // 
            // As a result, any code in the original flow of execution
            // that would attempt to access the ambient scope would end up 
            // with a null value since we removed the instance.
            //
            // It would be a fairly nasty bug to track down. So don't let
            // that happen. Hiding the ambient scope (i.e. clearing the CallContext
            // in our execution flow but leaving the ambient scope instance untouched)
            // is safe.
            DbContextScope.HideAmbientScope();
        }
        public DbContextScope(DbContextScopeOption joiningOption, bool readOnly, IsolationLevel? isolationLevel, IDbContextFactory dbContextFactory = null)
        {
            if (isolationLevel.HasValue && joiningOption == DbContextScopeOption.JoinExisting)
            {
                throw new ArgumentException("Cannot join an ambient DbContextScope when an explicit database transaction is required. When requiring explicit database transactions to be used (i.e. when the 'isolationLevel' parameter is set), you must not also ask to join the ambient context (i.e. the 'joinAmbient' parameter must be set to false).");
            }

            this.disposed = false;
            this.completed = false;
            this.readOnly = readOnly;

            this.parentScope = GetAmbientScope();
            if (this.parentScope != null && joiningOption == DbContextScopeOption.JoinExisting)
            {
                if (this.parentScope.readOnly && !this.readOnly)
                {
                    throw new InvalidOperationException("Cannot nest a read/write DbContextScope within a read-only DbContextScope.");
                }

                this.nested = true;
                this.dbContexts = this.parentScope.dbContexts;
            }
            else
            {
                this.nested = false;
                this.dbContexts = new DbContextCollection(readOnly, isolationLevel, dbContextFactory);
            }

            SetAmbientScope(this);
        }
Beispiel #3
0
        public DbContextScope(DbContextScopeOption joiningOption, bool readOnly, IsolationLevel?isolationLevel, IDbContextFactory dbContextFactory = null)
        {
            if (isolationLevel.HasValue && joiningOption == DbContextScopeOption.JoinExisting)
            {
                throw new ArgumentException("Cannot join an ambient DbContextScope when an explicit database transaction is required. When requiring explicit database transactions to be used (i.e. when the 'isolationLevel' parameter is set), you must not also ask to join the ambient context (i.e. the 'joinAmbient' parameter must be set to false).");
            }

            this.disposed  = false;
            this.completed = false;
            this.readOnly  = readOnly;

            this.parentScope = GetAmbientScope();
            if (this.parentScope != null && joiningOption == DbContextScopeOption.JoinExisting)
            {
                if (this.parentScope.readOnly && !this.readOnly)
                {
                    throw new InvalidOperationException("Cannot nest a read/write DbContextScope within a read-only DbContextScope.");
                }

                this.nested     = true;
                this.dbContexts = this.parentScope.dbContexts;
            }
            else
            {
                this.nested     = false;
                this.dbContexts = new DbContextCollection(readOnly, isolationLevel, dbContextFactory);
            }

            SetAmbientScope(this);
        }
        public void Dispose()
        {
            if (this.disposed)
            {
                return;
            }

            if (this.savedScope != null)
            {
                DbContextScope.SetAmbientScope(this.savedScope);
                this.savedScope = null;
            }

            this.disposed = true;
        }
        public void Dispose()
        {
            if (this.disposed)
            {
                return;
            }

            if (this.savedScope != null)
            {
                DbContextScope.SetAmbientScope(this.savedScope);
                this.savedScope = null;
            }

            this.disposed = true;
        }
Beispiel #6
0
        /*
         * This is where all the magic happens. And there is not much of it.
         *
         * This implementation is inspired by the source code of the
         * TransactionScope class in .NET 4.5.1 (the TransactionScope class
         * is prior versions of the .NET Fx didn't have support for async
         * operations).
         *
         * In order to understand this, you'll need to be familiar with the
         * concept of async points. You'll also need to be familiar with the
         * ExecutionContext and CallContext and understand how and why they
         * flow through async points. Stephen Toub has written an
         * excellent blog post about this - it's a highly recommended read:
         * http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx
         *
         * Overview:
         *
         * We want our DbContextScope instances to be ambient within
         * the context of a logical flow of execution. This flow may be
         * synchronous or it may be asynchronous.
         *
         * If we only wanted to support the synchronous flow scenario,
         * we could just store our DbContextScope instances in a ThreadStatic
         * variable. That's the "traditional" (i.e. pre-async) way of implementing
         * an ambient context in .NET. You can see an example implementation of
         * a TheadStatic-based ambient DbContext here: http://coding.abel.nu/2012/10/make-the-dbcontext-ambient-with-unitofworkscope/
         *
         * But that would be hugely limiting as it would prevent us from being
         * able to use the new async features added to Entity Framework
         * in EF6 and .NET 4.5.
         *
         * So we need a storage place for our DbContextScope instances
         * that can flow through async points so that the ambient context is still
         * available after an await (or any other async point). And this is exactly
         * what CallContext is for.
         *
         * There are however two issues with storing our DbContextScope instances
         * in the CallContext:
         *
         * 1) Items stored in the CallContext should be serializable. That's because
         * the CallContext flows not just through async points but also through app domain
         * boundaries. I.e. if you make a remoting call into another app domain, the
         * CallContext will flow through this call (which will require all the values it
         * stores to get serialized) and get restored in the other app domain.
         *
         * In our case, our DbContextScope instances aren't serializable. And in any case,
         * we most definitely don't want them to be flown accross app domains. So we'll
         * use the trick used by the TransactionScope class to work around this issue.
         * Instead of storing our DbContextScope instances themselves in the CallContext,
         * we'll just generate a unique key for each instance and only store that key in
         * the CallContext. We'll then store the actual DbContextScope instances in a static
         * Dictionary against their key.
         *
         * That way, if an app domain boundary is crossed, the keys will be flown accross
         * but not the DbContextScope instances since a static variable is stored at the
         * app domain level. The code executing in the other app domain won't see the ambient
         * DbContextScope created in the first app domain and will therefore be able to create
         * their own ambient DbContextScope if necessary.
         *
         * 2) The CallContext is flow through *all* async points. This means that if someone
         * decides to create multiple threads within the scope of a DbContextScope, our ambient scope
         * will flow through all the threads. Which means that all the threads will see that single
         * DbContextScope instance as being their ambient DbContext. So clients need to be
         * careful to always suppress the ambient context before kicking off a parallel operation
         * to avoid our DbContext instances from being accessed from multiple threads.
         *
         */

        /// <summary>
        /// Makes the provided 'dbContextScope' available as the the ambient scope via the CallContext.
        /// </summary>
        internal static void SetAmbientScope(DbContextScope newAmbientScope)
        {
            if (newAmbientScope == null)
            {
                throw new ArgumentNullException(nameof(newAmbientScope));
            }

            var current = CallContext.LogicalGetData(AmbientDbContextScopeKey) as InstanceIdentifier;

            if (current == newAmbientScope.instanceIdentifier)
            {
                return;
            }

            // Store the new scope's instance identifier in the CallContext, making it the ambient scope
            CallContext.LogicalSetData(AmbientDbContextScopeKey, newAmbientScope.instanceIdentifier);

            // Keep track of this instance (or do nothing if we're already tracking it)
            DbContextScopeInstances.GetValue(newAmbientScope.instanceIdentifier, key => newAmbientScope);
        }
        /*
         * This is where all the magic happens. And there is not much of it.
         * 
         * This implementation is inspired by the source code of the
         * TransactionScope class in .NET 4.5.1 (the TransactionScope class
         * is prior versions of the .NET Fx didn't have support for async
         * operations).
         * 
         * In order to understand this, you'll need to be familiar with the
         * concept of async points. You'll also need to be familiar with the
         * ExecutionContext and CallContext and understand how and why they 
         * flow through async points. Stephen Toub has written an
         * excellent blog post about this - it's a highly recommended read:
         * http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx
         * 
         * Overview: 
         * 
         * We want our DbContextScope instances to be ambient within 
         * the context of a logical flow of execution. This flow may be 
         * synchronous or it may be asynchronous.
         * 
         * If we only wanted to support the synchronous flow scenario, 
         * we could just store our DbContextScope instances in a ThreadStatic 
         * variable. That's the "traditional" (i.e. pre-async) way of implementing
         * an ambient context in .NET. You can see an example implementation of 
         * a TheadStatic-based ambient DbContext here: http://coding.abel.nu/2012/10/make-the-dbcontext-ambient-with-unitofworkscope/ 
         * 
         * But that would be hugely limiting as it would prevent us from being
         * able to use the new async features added to Entity Framework
         * in EF6 and .NET 4.5.
         * 
         * So we need a storage place for our DbContextScope instances 
         * that can flow through async points so that the ambient context is still 
         * available after an await (or any other async point). And this is exactly 
         * what CallContext is for.
         * 
         * There are however two issues with storing our DbContextScope instances 
         * in the CallContext:
         * 
         * 1) Items stored in the CallContext should be serializable. That's because
         * the CallContext flows not just through async points but also through app domain 
         * boundaries. I.e. if you make a remoting call into another app domain, the
         * CallContext will flow through this call (which will require all the values it
         * stores to get serialized) and get restored in the other app domain.
         * 
         * In our case, our DbContextScope instances aren't serializable. And in any case,
         * we most definitely don't want them to be flown accross app domains. So we'll
         * use the trick used by the TransactionScope class to work around this issue.
         * Instead of storing our DbContextScope instances themselves in the CallContext,
         * we'll just generate a unique key for each instance and only store that key in 
         * the CallContext. We'll then store the actual DbContextScope instances in a static
         * Dictionary against their key. 
         * 
         * That way, if an app domain boundary is crossed, the keys will be flown accross
         * but not the DbContextScope instances since a static variable is stored at the 
         * app domain level. The code executing in the other app domain won't see the ambient
         * DbContextScope created in the first app domain and will therefore be able to create
         * their own ambient DbContextScope if necessary.
         * 
         * 2) The CallContext is flow through *all* async points. This means that if someone
         * decides to create multiple threads within the scope of a DbContextScope, our ambient scope
         * will flow through all the threads. Which means that all the threads will see that single 
         * DbContextScope instance as being their ambient DbContext. So clients need to be 
         * careful to always suppress the ambient context before kicking off a parallel operation
         * to avoid our DbContext instances from being accessed from multiple threads.
         * 
         */

        /// <summary>
        /// Makes the provided 'dbContextScope' available as the the ambient scope via the CallContext.
        /// </summary>
        internal static void SetAmbientScope(DbContextScope newAmbientScope)
        {
            if (newAmbientScope == null)
            {
                throw new ArgumentNullException(nameof(newAmbientScope));
            }

            var current = CallContext.LogicalGetData(AmbientDbContextScopeKey) as InstanceIdentifier;
            if (current == newAmbientScope.instanceIdentifier)
            {
                return;
            }

            // Store the new scope's instance identifier in the CallContext, making it the ambient scope
            CallContext.LogicalSetData(AmbientDbContextScopeKey, newAmbientScope.instanceIdentifier);

            // Keep track of this instance (or do nothing if we're already tracking it)
            DbContextScopeInstances.GetValue(newAmbientScope.instanceIdentifier, key => newAmbientScope);
        }
 public DbContextReadOnlyScope(DbContextScopeOption joiningOption, IsolationLevel? isolationLevel, IDbContextFactory dbContextFactory = null)
 {
     this.internalScope = new DbContextScope(joiningOption: joiningOption, readOnly: true, isolationLevel: isolationLevel, dbContextFactory: dbContextFactory);
 }
Beispiel #9
0
 public DbContextReadOnlyScope(DbContextScopeOption joiningOption, IsolationLevel?isolationLevel, IDbContextFactory dbContextFactory = null)
 {
     this.internalScope = new DbContextScope(joiningOption : joiningOption, readOnly : true, isolationLevel : isolationLevel, dbContextFactory : dbContextFactory);
 }
 public void ShouldFailWhenNestingReadWriteScopeInReadOnlyScope()
 {
     using (var dbReadOnlyContextScope = new DbContextScope(true, null))
     {
         using (var dbReadWriteContextScope = new DbContextScope(null))
         {
             Assert.Fail();
         }
     }
 }
 public void ShouldFailWhenJoiningToDbTransaction()
 {
     using (var dbContextScope = new DbContextScope(DbContextScopeOption.JoinExisting, false, IsolationLevel.ReadUncommitted, null))
     {
         Assert.Fail();
     }
 }
        public TDbContext Get <TDbContext>() where TDbContext : class, IDbContext
        {
            var ambientDbContextScope = DbContextScope.GetAmbientScope();

            return(ambientDbContextScope?.DbContexts.Get <TDbContext>());
        }