U4-9855 - Database.GetTransaction() throws a YSOD null reference exception

Created by Shannon Deminick 04 May 2017, 02:52:03 Updated by Shannon Deminick 24 May 2017, 08:01:27

For example, this code in UmbracoIdentity project throws


        /// <summary>
        /// Checks if the table exists and if not it creates it
        /// </summary>
        private void EnsureTable()
        {
            var schemaHelper = new DatabaseSchemaHelper(_db, _logger, _sqlSyntaxProvider);
            if (!schemaHelper.TableExist(TableName))
            {
                using (var transaction = _db.GetTransaction())
                {
                    schemaHelper.CreateTable<ExternalLoginDto>();
                    transaction.Complete();
                }
                LogHelper.Info<ExternalLoginStore>(string.Format("New table '{0}' was created", TableName));
            }
        }

When the _db.GetTransaction() is used a null ref exception occurs, the stack trace doesn't really say much:

Server Error in '/' Application.

Object reference not set to an instance of an object.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error: 


Line 100:
Line 101:            // Sign in the user with this external login provider if the user already has a login
Line 102:            var user = await UserManager.FindAsync(loginInfo.Login);
Line 103:            if (user != null)
Line 104:            {

Source File: x:\Projects\Umbraco\UmbracoIdentity\src\UmbracoIdentity.Web\App_Code\Controllers\UmbracoIdentityAccountController.cs    Line: 102 

Stack Trace: 


[NullReferenceException: Object reference not set to an instance of an object.]
   Umbraco.Core.Persistence.Database.BeginTransaction(IsolationLevel isolationLevel) +119
   Umbraco.Core.Persistence.Database.GetTransaction() +55
   UmbracoIdentity.ExternalLoginStore.EnsureTable() +204
   UmbracoIdentity.ExternalLoginStore.<EnsureInitialized>b__12_0() +62
   System.Threading.LazyInitializer.EnsureInitializedCore(T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory) +140
   UmbracoIdentity.ExternalLoginStore.EnsureInitialized() +125
   UmbracoIdentity.ExternalLoginStore.Find(UserLoginInfo login) +78
   UmbracoIdentity.UmbracoMembersUserStore`1.FindAsync(UserLoginInfo login) +119
   UmbracoIdentity.Web.Controllers.<ExternalLoginCallback>d__4.MoveNext() in x:\Projects\Umbraco\UmbracoIdentity\src\UmbracoIdentity.Web\App_Code\Controllers\UmbracoIdentityAccountController.cs:102
   System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +14139120
   System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +62
   System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult) +93
   System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeAsynchronousActionMethod>b__36(IAsyncResult asyncResult) +22
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) +42
   System.Web.Mvc.Async.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d() +72
   System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +386
   System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +386
   System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +386
   System.Web.Mvc.Async.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() +386
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +42
   System.Web.Mvc.Async.<>c__DisplayClass2b.<BeginInvokeAction>b__1c() +38
   System.Web.Mvc.Async.<>c__DisplayClass21.<BeginInvokeAction>b__1e(IAsyncResult asyncResult) +186
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +38
   System.Web.Mvc.Controller.<BeginExecuteCore>b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState) +29
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +67
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +53
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +36
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +38
   System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +44
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +67
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +38
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +399
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +157

Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.6.1637.0

Comments

Shannon Deminick 04 May 2017, 03:12:53

The problem is the way that UmbracoIdentity used the Database instance. In 7.6 this is more apparent that i was 'doing it wrong'. A Database instance is scoped, so it will either live on the current thread or on the current request, it does not live for the lifespan of the application - and it should definitely not! Unfortunately UmbracoIdenity was storing a reference to the Database instance created on startup which means it wouldn't let it go and was trying to reuse the same instance over and over but it would have already been disposed so now it's stale.

You shouldn't store references to the actual Database instance it needs to be resolved when needed from the DatabaseContext.


VitDas 24 May 2017, 07:58:18

Hi @Shandem, does it mean that also I can't store DatabaseSchemaHelper to private variable in ctor? private readonly DatabaseSchemaHelper _dbHelper; public SomeClass { _dbHelper = new DatabaseSchemaHelper(ApplicationContext.Current.DatabaseContext.Database, LoggerResolver.Current.Logger, ApplicationContext.Current.DatabaseContext.SqlSyntax);

And must create it right before usage? var databaseSchemaHelper = new DatabaseSchemaHelper(ApplicationContext.Current.DatabaseContext.Database, LoggerResolver.Current.Logger, ApplicationContext.Current.DatabaseContext.SqlSyntax); databaseSchemaHelper.CreateTable();


Shannon Deminick 24 May 2017, 08:01:27

Yup, you don't want to hang on to a Database instance, these are scoped instances


Priority: Normal

Type: Bug

State: Workaround posted

Assignee:

Difficulty: Normal

Category:

Backwards Compatible: True

Fix Submitted:

Affected versions: 7.6.0

Due in version:

Sprint:

Story Points:

Cycle: 1