Code in TransactionScope block generates NullReferenceException.

Posts   
 
    
PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 05-Sep-2006 08:40:26   

In version 2 the following code construct fails: - Open a transaction scope - Read an entity or create a new instance. - Change data of the entity. - Save the entity. - When trying to access data of the uncommitted entity, I get a NullReference exception.

See the following code example.


using (new TransactionScope())
{
      SubscriberEntity subscriber = new SubscriberEntity();
      subscriber.FetchUsingPK(1);

      subscriber.LastName = "Gates";

      subscriber.Save(true);

      // Now accessing a member of the entity fails with a NullReferenceException.
      string name = subscriber.LastName;
}

The NullReferenceException for this code is:


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

at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.ExecuteSingleRowRetrievalQuery(IRetrievalQuery queryToExecute, ITransaction containingTransaction, IEntityFields fieldsToFill) 
at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.PerformFetchEntityAction(IEntity entityToFetch, ITransaction containingTransaction, IPredicateExpression selectFilter, IPrefetchPath prefetchPathToUse, Context contextToUse) 
at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.FetchExisting(IEntity entityToFetch, ITransaction containingTransaction, IPrefetchPath prefetchPathToUse, Context contextToUse) 
at DataAccessLayer.EntityClasses.SubscriberEntity.Fetch(Int32 subscriberId, IPrefetchPath prefetchPathToUse, Context contextToUse) in SubscriberEntity.cs:line 1347 
at DataAccessLayer.EntityClasses.SubscriberEntity.Refetch() in SubscriberEntity.cs:line 499 
at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.CheckForRefetch() 
at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.GetCurrentFieldValue(Int32 fieldIndex) 
at DataAccessLayer.EntityClasses.SubscriberEntity.get_LastName() in SubscriberEntity.cs:line 1554 

In version 1, this worked perfectly.

Regards,

Patrick

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Sep-2006 10:39:59   

The read of the property forces the selfservicing entity to refetch itself, the exception occurs inside the fetch routine. I'll check it out.

(Sqlserver 2000 or sqlserver2005 ? I pressume the latter, due to the transaction scope)

Frans Bouma | Lead developer LLBLGen Pro
PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 05-Sep-2006 10:53:09   

Otis wrote:

(Sqlserver 2000 or sqlserver2005 ? I pressume the latter, due to the transaction scope)

Yep, its SqlServer 2005.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Sep-2006 12:30:24   

On Sqlserver2005, can't reproduce it: (Adventureworks. It uses DTC, as it crashes rightfully when DTC isn't started)

[Test]
public void InsertNewWithTransactionScope()
{
    using(new TransactionScope())
    {
        CreditCardEntity ccard = new CreditCardEntity();
        ccard.FetchUsingPK(1);
        int expYear = ccard.ExpYear;
        ccard.ExpYear += 1;

        Assert.IsTrue(ccard.Save());
        int newExpYear = ccard.ExpYear;
        Assert.AreEqual(expYear + 1, newExpYear);
    }
}

Btw, your code always rolls back the transaction as you don't Complete it. (or your code isn't complete).

Does your record with pk '1' exist?

Frans Bouma | Lead developer LLBLGen Pro
PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 05-Sep-2006 12:43:08   

Otis wrote:

Btw, your code always rolls back the transaction as you don't Complete it. (or your code isn't complete).

Does your record with pk '1' exist?

I use this scenario for unit testing and therefore I never commit the transaction (to rollback the database to its original state for the next test).

I realize the fetch in my example introduces some unnecessary blur to the problem description, because when I create a new row in the table and try to read the property, I get the same exception.

Note that this same exception occurs in each and every unit test that performs a save for each entity type. The unit tests work fine with v1 and have not been modified.

Please let me know what info I can supply for further investigation.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Sep-2006 12:54:50   

As the routine the error occurs in is very straight forward, the only place it can crash with a nullref exception is when the connection in the query object passed in is null. This is not possible unless something odd is going on.

Could you check for me if you by accident have referenced the CF20 version of hte ormsupportclasses dll?

Frans Bouma | Lead developer LLBLGen Pro
PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 05-Sep-2006 13:45:05   

Otis wrote:

As the routine the error occurs in is very straight forward, the only place it can crash with a nullref exception is when the connection in the query object passed in is null. This is not possible unless something odd is going on.

Could you check for me if you by accident have referenced the CF20 version of hte ormsupportclasses dll?

No, the reference is OK.

Concerning the connection; When debugging, I notice that when the call to dao.FetchExisting() in Fetch() is performed (where the exception occurs), property base.Transaction.ConnectionToUse is null. Property base.Transaction.ConnectionString contains the correct connection string.

The connection string is stored in app.config as follows:


  <appSettings>
    <add key="Main.ConnectionString" value="data source=(local)\SQLEXPRESS;initial catalog=Customer;integrated security=SSPI;persist security info=False;packet size=4096"/>
  </appSettings>

PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 05-Sep-2006 14:01:06   

After investigating all my unit tests again, I discovered something I did not notice yet.

All unit tests fail, except for a few. The successful unit tests are those on tables which have no relationships defined (not in the database and not by the designer).

When debugging the failing tests, I see that before the exception occurs, the debugger correctly shows the values of the properties (as in my example also the LastName property) so it can retrieve their values. However, the properties pointing to related entities all throw a null reference exception. Could this be the null reference exception being thrown?

As you see in my example, I do not use a prefetch path.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Sep-2006 14:34:13   

It should load the related entities through lazy loading... If you set a breakpoint in the getter of the property which represents the related entity (e.g. myOrder.Customer) and step into the method, where does it crash?

The stacktrace you posted does suggest a null ref in the single entity fetch code. This is the routine:


/// <summary>
/// Executes the passed in retrieval query and, if not null, runs it inside the passed in transaction. Used to read 1 row.
/// </summary>
/// <param name="queryToExecute">Retrieval query to execute</param>
/// <param name="containingTransaction">A containing transaction if caller is added to a transaction, or null of not.</param>
/// <param name="fieldsToFill">The IEntityFields object to store the fetched data in</param>
public virtual void ExecuteSingleRowRetrievalQuery(IRetrievalQuery queryToExecute, ITransaction containingTransaction, IEntityFields fieldsToFill)
{
    TraceHelper.WriteLineIf(TraceHelper.PersistenceExecutionSwitch.TraceInfo, "DaoBase.ExecuteSingleRowRetrievalQuery", "Method Enter");

    WireTransaction(queryToExecute, containingTransaction);

    bool isConnectionOpenedExternal = (queryToExecute.Connection.State == ConnectionState.Open);
    IDataReader dataSource = null;
    try
    {
        if(!isConnectionOpenedExternal)
        {
            if( queryToExecute.Connection.State != ConnectionState.Open )
            {
                queryToExecute.Connection.Open();
            }
        }

        dataSource = queryToExecute.Execute(CommandBehavior.SingleRow);
        FetchOneRow(dataSource, fieldsToFill);
    }
        // all exceptions are fatal.
    finally
    {
        if(dataSource!=null)
        {
            if(!dataSource.IsClosed)
            {
                dataSource.Close();
            }
            dataSource.Dispose();
        }
        if(!isConnectionOpenedExternal)
        {
            queryToExecute.Connection.Close();
        }

        TraceHelper.WriteLineIf(TraceHelper.PersistenceExecutionSwitch.TraceInfo, "DaoBase.ExecuteSingleRowRetrievalQuery", "Method Exit");
    }
}

As you can see, there's little room for nullreferences, I can only suspect the error to happen at the line where the connection is referenced in queryToExecute, however that would be very weird.

Frans Bouma | Lead developer LLBLGen Pro
PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 05-Sep-2006 14:38:29   

I have edited the example I gave you. Indeed, that example works. The exception only occurs when a recursive save is performed. In my example I by accident left out the true boolean in the Save(), because actually the code is part of a Business Layer, and I had to strip it from the Business Object (where the Save was encapsulated in a parameterless Save). I am truly sorry.

I have now made a separate unit test which directly calls the data layer and fails. Below is an exact copy of this unit test:


    [Test(Description = "TEST")]
    public void TEST()
    {
      SubscriberEntity subscriber = new SubscriberEntity();
      subscriber.FetchUsingPK(1);

      subscriber.LastName = "Gates";

      subscriber.Save(true);

      // Now accessing a member of the entity fails with a NullReferenceException.
      string name = subscriber.LastName;
      int ID = subscriber.SubscriberId;
    }

And the exception:


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

at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.ExecuteSingleRowRetrievalQuery(IRetrievalQuery queryToExecute, ITransaction containingTransaction, IEntityFields fieldsToFill) 
at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.PerformFetchEntityAction(IEntity entityToFetch, ITransaction containingTransaction, IPredicateExpression selectFilter, IPrefetchPath prefetchPathToUse, Context contextToUse) 
at SD.LLBLGen.Pro.ORMSupportClasses.DaoBase.FetchExisting(IEntity entityToFetch, ITransaction containingTransaction, IPrefetchPath prefetchPathToUse, Context contextToUse) 
at DataAccessLayer.EntityClasses.SubscriberEntity.Fetch(Int32 subscriberId, IPrefetchPath prefetchPathToUse, Context contextToUse) in SubscriberEntity.cs:line 1347 
at DataAccessLayer.EntityClasses.SubscriberEntity.Refetch() in SubscriberEntity.cs:line 499 
at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.CheckForRefetch() 
at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase.GetCurrentFieldValue(Int32 fieldIndex) 
at DataAccessLayer.EntityClasses.SubscriberEntity.get_LastName() in SubscriberEntity.cs:line 1554 
at TestSuite.TimeDiagramFixture.TEST() in TimeDiagramFixture.cs:line 393 

So when the true is removed from Save() and thus not a recursive save is performed, this method succeeds.

PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 05-Sep-2006 15:08:17   

Otis wrote:

It should load the related entities through lazy loading... If you set a breakpoint in the getter of the property which represents the related entity (e.g. myOrder.Customer) and step into the method, where does it crash?

It crashes on exactly the same line.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Sep-2006 18:38:15   

When I specify true I can reproduce it. Will look into it.

edit: it crashes here: bool isConnectionOpenedExternal = (queryToExecute.Connection.State == ConnectionState.Open);

so the connection of the queryToExecute is null. I'll check what the problem is.

(edit). Something REALLY strange is going on... I'm stepping through the code, and from one statement to the other (setting a boolean!), this.Transaction (this == my entityobject I'm saving) becomes null. sigh.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 05-Sep-2006 19:23:35   

Ok, The problem is this. As you don't start an explicit LLBLGen Pro transaction, one is started for you, namely by the Save() routine in the entity you save.

The save routine at the end disposes the Transaction object. This also tosses away the resourcemanager which manages the transaction because of System.Transactions.

This goes wrong when the transaction is actually needed again within the scope, as you do.

This is a bug in the code, I think I can fix it, as it's also fixed for adapter, which had the same problem, so I'll look into how I can fix this properly.

A workaround exists: within the using statement which creates the scope, use a using statement which creates an LLBLGen Pro transaction, like this:


[Test]
public void InsertNewWithTransactionScope()
{
    using(TransactionScope ts = new TransactionScope())
    {
        using(Transaction t = new Transaction(System.Data.IsolationLevel.ReadCommitted, "Test"))
        {
            CreditCardEntity ccard = new CreditCardEntity();
            t.Add(ccard);

            ccard.FetchUsingPK(1);
            int expYear = ccard.ExpYear;
            ccard.ExpYear += 1;

            Assert.IsTrue(ccard.Save(true));
            int newExpYear = ccard.ExpYear;
            Assert.AreEqual(expYear + 1, newExpYear);
            t.Commit();
        }
        ts.Complete();
    }
}

(Transaction is the generated Transaction class, you likely have to prefix it with the namespace).

Frans Bouma | Lead developer LLBLGen Pro
PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 06-Sep-2006 09:08:15   

The workaround works, however it is unfeasable for me to make this change to all of my unit tests (which are a lot). When you fixed the bug, how long will it take for this fix to be published? I mean, can it be published directly or has it to be included in a bug release? Because I am a bit stuck right now... disappointed

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 06-Sep-2006 09:38:24   

It's planned to get fixed today, so the fix will be out today or tomorrow. We don't stall releases of bugfixes till doomsday like some other software vendors do simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 06-Sep-2006 10:41:27   

It's a template bug.

in SqlServerSpecific\C#\dbUtils.template,

change in DetermineConnectionToUse if(containingTransaction!=null)

into if((containingTransaction!=null)&&(containingTransaction.ConnectionToUse!=null))

Fixed in next build.

Frans Bouma | Lead developer LLBLGen Pro
PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 06-Sep-2006 11:48:48   

smile Great! Thanx for the fast solution and of course your great product in general!

PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 08-Sep-2006 12:09:38   

Otis wrote:

It's planned to get fixed today, so the fix will be out today or tomorrow. We don't stall releases of bugfixes till doomsday like some other software vendors do simple_smile

Can you give me an indication when the fix will be available. It is very important for me to be able to run my unit tests against v2 to check for compatibility issues. Tight schedules and that kind of things...disappointed

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 08-Sep-2006 12:24:42   

PatrickD wrote:

Otis wrote:

It's planned to get fixed today, so the fix will be out today or tomorrow. We don't stall releases of bugfixes till doomsday like some other software vendors do simple_smile

Can you give me an indication when the fix will be available. It is very important for me to be able to run my unit tests against v2 to check for compatibility issues. Tight schedules and that kind of things...disappointed

Today (friday). However if you make the change to the templates yourself, it will be identical to what's released later today.

Frans Bouma | Lead developer LLBLGen Pro
PatrickD
User
Posts: 65
Joined: 05-Sep-2006
# Posted on: 08-Sep-2006 12:46:33   

Otis wrote:

However if you make the change to the templates yourself, it will be identical to what's released later today.

That worked! Thanks.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 08-Sep-2006 16:43:53   

It's available simple_smile

Frans Bouma | Lead developer LLBLGen Pro