Problem with Transactions on DataAccessAdapter

Posts   
 
    
Posts: 6
Joined: 25-Jan-2007
# Posted on: 25-Jan-2007 13:20:34   

LLBL Gen Pro Version: 2.0.0.0 Final

Runtime Version: 2.0.0.61107

Exception: System.InvalidOperationException: No transaction in progress

Stack Trace:

 at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
   at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at NUnit.Core.TestMethod.doTearDown(TestCaseResult testResult)
--TargetInvocationException
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.Rollback(String savePointName)
   at Wm.Server.Data.DataAdapter.EndTesting() in S:\Wm\Wm\Wm.Server\Data\DataAdapter.cs:line 49
   at Wm.Server.Data.Tests.DataAdapterTest.TearDown() in S:\Wm\Wm\Wm.Server\Data\Tests\DataAdapterTest.cs:line 32

Template Group: Adapter

.NET Version: 2.0


Hi,

we are having a problem with rolling back transactions using the DataAccessAdapter.

** This code works - i.e. the record is removed from the database after the Test completes **



        [Test]
        public void CreateFootballClub()
        {
            using (DataAccessAdapter adapter = new DataAccessAdapter())
            {
                try
                {
                    adapter.StartTransaction(IsolationLevel.ReadCommitted, "TestTran");

                    EntityCollection<FootballClubEntity> footballClubs = new EntityCollection<FootballClubEntity>();

                    adapter.FetchEntityCollection(footballClubs, null);

                    Assert.AreEqual(0, footballClubs.Count);

                    FootballClubEntity footballClubEntity = new FootballClubEntity();
                    footballClubEntity.Name = "Liverpool";
                    footballClubEntity.ManagerName = "Rafael Benitez";
                    footballClubEntity.Division = "Premier League";

                    adapter.SaveEntity(footballClubEntity, true);

                    FootballClubEntity savedFootballClubEntity = new FootballClubEntity(footballClubEntity.Id);
                    adapter.FetchEntity(savedFootballClubEntity);

                    Assert.AreEqual("Liverpool", savedFootballClubEntity.Name);
                }
                finally
                {
                    adapter.Rollback();
                }
            }
        }



In our production code we are using a class which gives us data adapters. Here is the class:



    interface IDataAdapter
    {
        DataAccessAdapter Adapter { get; }
        void BeginTesting();
        void EndTesting();
    }
    
    class DataAdapter : IDataAdapter
    {
        public static readonly IDataAdapter Instance = new DataAdapter();

        DataAccessAdapter adapter;
        bool testing;
        
        DataAdapter() {}

        public DataAccessAdapter Adapter
        {
            get 
            {
                if (testing)
                {
                    if (adapter == null)
                    {
                        adapter = new DataAccessAdapter();
                    }

                    return adapter;
                }

                return new DataAccessAdapter(); 
            }
        }

        public void BeginTesting()
        {
            testing = true;
            Adapter.StartTransaction(IsolationLevel.ReadCommitted, "Test Transaction");
    
        }

        public void EndTesting()
        {
            Adapter.Rollback();
            testing = false;
        }
    }



The theory here is that when we are writing tests we can Begin and End Testing thus enrolling a transaction and rollback test data. In production, the singleton will always give us a new adapter when DataAdapter.Instance.Adapter is called. We call it through a class called ServiceBase like this:



class FootballClubManager : ServiceBase
{
        public FootballClubEntity AddWorkflow(FootballClubEntity footballClubEntity)
        {
            using (DataAccessAdapter adapter = GetAdapter())
            {
                adapter.SaveEntity(footballClubEntity, true);
            }
            return footballClubEntity;
        }
}

class ServiceBase : IServiceBase
{
        public DataAccessAdapter GetAdapter()
        {
            return DataAdapter.Instance.Adapter;
        }
        .
        .
        // continues
}


However, when we run a test like the one given below we get the exception given above. Note: in our test we call directly into DataAdapter.Instance.Adapter rather than through the ServiceBase class - the reason for this is we are trying to isolate the reason why we are getting the exception.



    [TestFixture]
    public class DataAdapterTest
    {
        [SetUp]
        public void SetUp()
        {
            DataAdapter.Instance.BeginTesting();
        }

        [TearDown]
        public void TearDown()
        {
            DataAdapter.Instance.EndTesting();   
        }

        [Test]
        public void CreateFootballClub()
        {
            FootballClubEntity liverpool = new FootballClubEntity();
            liverpool.Name = "Liverpool FC";
            liverpool.ManagerName = "Sir Rafael Benitez";
            liverpool.Division = "Premier League";

            // there shouldn't be any in the db to start with
            EntityCollection<FootballClubEntity> footballClubs = new EntityCollection<FootballClubEntity>();
            using (DataAccessAdapter adapter = DataAdapter.Instance.Adapter)
            {
                adapter.FetchEntityCollection(footballClubs, null);
            }

            Assert.AreEqual(0, footballClubs.Count);

            // now create a new football club
            using (DataAccessAdapter adapter = DataAdapter.Instance.Adapter)
            {
                adapter.SaveEntity(liverpool, true);
            }

            // re-fetch the saved club
            FootballClubEntity liverpoolSaved = new FootballClubEntity(liverpool.Id);
            using (DataAccessAdapter adapter = DataAdapter.Instance.Adapter)
            {
                adapter.FetchEntity(liverpoolSaved);
            }

            // check it was saved correctly
            Assert.IsTrue(liverpoolSaved.Id > 0);
            Assert.AreEqual("Liverpool FC", liverpoolSaved.Name);
        }




When Adapter.Instance.EndTesting looks like this...



public void EndTesting()
        {
            Adapter.Rollback();
            testing = false;
        }


...the test passes but the record created is not removed from the database.

When Adapter.Instance.EndTesting looks like this...



public void EndTesting()
        {
            Adapter.Rollback("Test Transaction");
            testing = false;
        }


...we get the exception. I understand that this is because "Test Transaction" is the name of the transaction rather than a particular SavePoint. However, I would expect a direct call the Adapter.Rollback() to roll everything back.

Thank you for your help.

Steve Wood Webmastermedia

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 25-Jan-2007 14:50:08   

I would expect a direct call the Adapter.Rollback() to roll everything back.

The Adapter.Rollback() methods should Roll back everything that has been executed since the StartTransaction was called.

When Adapter.Instance.EndTesting looks like this...

Code: public void EndTesting() { Adapter.Rollback(); testing = false; } ...the test passes but the record created is not removed from the database.

Unfortunatly I can udge this. You have posted a test code that uses multiple instances of the supposed to be Singleton class, and I can't see where in the test code Does the transaction Start and End.

Where are the calls to BeginTesting() & EndTesting()?

Posts: 6
Joined: 25-Jan-2007
# Posted on: 25-Jan-2007 15:01:48   

If you look in the [TestFixture] code I supplied you can see



[SetUp]
public void SetUp()
{
    DataAdapter.Instance.BeginTesting();
}

[TearDown]
public void TearDown()
{
    DataAdapter.Instance.EndTesting(); 
}



In NUnit the SetUp() method is called before a Test runs and and the TearDown() method is called after a Test runs. So, in a single [TestFixture] with 3 Tests, [SetUp] and [TearDown] would be called 3 times.

Thanks,

Steve Wood Webmastermedia

Posts: 6
Joined: 25-Jan-2007
# Posted on: 25-Jan-2007 15:12:26   

We have just noticed that after the call to:



// now create a new football club
using (DataAccessAdapter adapter = DataAdapter.Instance.Adapter)
{
    adapter.SaveEntity(liverpool, true);
}


inside our Test, two properties change on the DataAdapter:

TranactionIsolationLevel was "ReadUncommitted" and is now "ReadCommitted" TransactionName was "Test Transaction" and is now "RecursiveSave"

Irrespective of a nested transaction being created inside the Data Adapter by LLBL Gen code, I would expect Rollback to roll everything back. Is that correct?

Thanks,

Steve Wood Webmastermedia

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 26-Jan-2007 10:41:02   

How you've setup the test setup/teardown isn't recommended. This is because it's undefined how the 3 elements which make up your test: Setup, test and teardown are executed. They should be ATOMIC, which means that the Setup should set things up but NOT in such a way that it sets up STATE which is used in the TEST. The Test is unaware of any state, has no reliance on order or whatsoever, a test is atomic.

Setup and Teardown could be used in the following way: Setup inserts data into the database. Test then runs, test uses data in the database Teardown deletes all data in the database

This way, Test doesn't rely on any object/state created in Setup or Teardown. The reason this is important is that the code is called via remoting (as all tests run inside a different appdomain!) and perhaps called on different threads.

So if you want to do this more solidly, you should create a simply base class for your test classes, which has 2 methods which are used by all tests: one is used to start a transaction and one is used to end the transaction, though as these are oneliners, I don't really see how you wouldn't place them inside your testroutines, as it makes the Test routines really atomic: they don't rely on STATE created in objects in memory by other routines, which is key for solid unittests.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 6
Joined: 25-Jan-2007
# Posted on: 26-Jan-2007 12:37:15   

Thanks for your reply, Otis.

I agree with your commments. I guess the problem we're trying to solve is how best can we write Unit Tests that test the functionality of our server side classes AND roll back the data these classes are creating. For instance, the following test works correctly (i.e. the data is rolled back):



[Test]
public void CreateFootballClubWithSingletonDataAdapter()
{
    DataAdapter.Instance.BeginTesting();

    FootballClubEntity wolves = new FootballClubEntity();
    wolves.Name = "Wolves FC";
    wolves.ManagerName = "Mick McCarthy";
    wolves.Division = "The Championship";

    // there shouldn't be any in the db to start with
    EntityCollection<FootballClubEntity> footballClubs = new EntityCollection<FootballClubEntity>();
    DataAdapter.Instance.Adapter.FetchEntityCollection(footballClubs, null);

    Assert.AreEqual(0, footballClubs.Count);

    // now create a new football club
    DataAdapter.Instance.Adapter.SaveEntity(wolves, true);

    // re-fetch the saved club
    FootballClubEntity wolvesSaved = new FootballClubEntity(wolves.Id);
    DataAdapter.Instance.Adapter.FetchEntity(wolvesSaved);

    // check it was saved correctly
    Assert.IsTrue(wolvesSaved.Id > 0);
    Assert.AreEqual("wolves FC", wolvesSaved.Name);

    DataAdapter.Instance.EndTesting();
}



However, what we really want to be able to test is that our "Business Layer" classes are working correctly. To do this, we would like to create an instance of the Business Layer class, call the methods on it and then, when the test has finished, have the data roll back. Continuing with out Football Club theme, here is an example of the FootballClubManager:



public interface IFootballClubManager
{
    EntityCollection<FootballClubEntity> GetFootballClubs();
    FootballClubEntity CreateFootballClub(string clubName, string managerName, string divisionName);
}

class FootballClubManager : ServiceBase, IFootballClubManager
{
    public EntityCollection<FootballClubEntity> GetFootballClubs()
    {
        EntityCollection<FootballClubEntity> footballClubs = new EntityCollection<FootballClubEntity>();

        using (DataAccessAdapter adapter = GetAdapter())
        {
            adapter.FetchEntityCollection(footballClubs, null);
        }

        return footballClubs;
    }

    public FootballClubEntity CreateFootballClub(string clubName, string managerName, string divisionName)
    {
        FootballClubEntity footballClubEntity = new FootballClubEntity();
        footballClubEntity.Name = clubName;
        footballClubEntity.ManagerName = managerName;
        footballClubEntity.Division = divisionName;

        using (DataAccessAdapter adapter = GetAdapter())
        {
            adapter.SaveEntity(footballClubEntity, true);               
        }

        return footballClubEntity;
    }
}


Here is an extract from Service Base:



interface IServiceBase
{
    DataAccessAdapter GetAdapter();
}

class ServiceBase : IServiceBase
{
    public DataAccessAdapter GetAdapter()
    {
        return DataAdapter.Instance.Adapter;
    }
}


Here is DataAdapter:



interface IDataAdapter
{
    DataAccessAdapter Adapter { get; }
    void BeginTesting();
    void EndTesting();
}

class DataAdapter : IDataAdapter
{
    public static readonly IDataAdapter Instance = new DataAdapter();

    DataAccessAdapter adapter;
    bool testing;
    
    DataAdapter() {}

    public DataAccessAdapter Adapter
    {
        get 
        {
            if (testing)
            {
                if (adapter == null)
                {
                    adapter = new DataAccessAdapter();
                }

                return adapter;
            }

            return new DataAccessAdapter(); 
        }
    }

    public void BeginTesting()
    {
        testing = true;
        Adapter.StartTransaction(IsolationLevel.ReadUncommitted, "Test Transaction");
    }

    public void EndTesting()
    {
        Adapter.Rollback();
        testing = false;
    }
}


And, finally, here is our Unit Test:



[TestFixture]
public class FootballClubManagerTest
{
    IFootballClubManager footballClubManager;

    [SetUp]
    public void SetUp()
    {
        DataAdapter.Instance.BeginTesting();

        footballClubManager = new FootballClubManager();
    }

    [TearDown]
    public void TearDown()
    {
        DataAdapter.Instance.EndTesting();
    }

    [Test]
    public void CreateFootballClub()
    {
        Assert.AreEqual(0, footballClubManager.GetFootballClubs().Count);

        FootballClubEntity footballClubEntity = footballClubManager.CreateFootballClub("Leeds", "Dennis Wise", "The Championship");

        Assert.AreEqual(1, footballClubManager.GetFootballClubs().Count);

        Assert.IsTrue(footballClubEntity.Id > 0);
        Assert.AreEqual("Leeds", footballClubEntity.Name);
    }
}


The test succeeds, but the data is not rolled back. In the debugger, the SetUp, Test and TearDown all run on the same thread. It is only after the call to IFootballClubManager.CreateFootballClub(), where SaveEntity is called on the DataAdapter, that the Transaction details change. It would seem that another transaction is being created and committed inside LLBL Gen Pro code and that when Rollback() is called in TearDown there is nothing to rollback.

If this is "by design", then could you please advise me what (based on your knowledge of the design of LLBL Gen Pro) would be the best way to architect a way of writing tests that create and rollback test data when calling BL classes?

Thanks for your time,

Steve Wood Webmastermedia

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 26-Jan-2007 17:45:50   

Aren't you doing the same thing as with your original post: that you start/end the transaction in Setup/Teardown, which won't work as I explained?

One reason also could be that it goes wrong is that the second appDomain might be destroyed after Setup is done. I don't know, nor should it be important I think, the test should be atomic.

What you could do is simply write a routine which whipes the db in the fixture tear down (that's what I do), and in the fixture setup you could insert testdata for the tests to operate on.

The main issue is: you want to keep an open transaction after the test method ends. That's not what you should do. When the method ends the transactions etc. should be ended. Cleanup of everything is then done by the fixture teardown (or teardown itself) however not through a rollback, but through a set of deletes directly on the db (without a filter)

Frans Bouma | Lead developer LLBLGen Pro
Posts: 6
Joined: 25-Jan-2007
# Posted on: 26-Jan-2007 18:26:00   

I'm not certain I've explained my problem to you clearly. If that's the case, I apologise.

My issue is not that I wish to create test data in a SetUp, then use that data in a Test and then rollback that data in a TearDown. What I want to do is test that my BL classes are creating and updating data in the way that I expect. To this end, I want to be able to write Tests that:

  1. call a BL class; and
  2. then roll back the data that the BL class created/updated.

Therefore, my Test needs to make use of the same Data Adapter that the BL class is using in order to be able to rollback the transaction.

If I can't do this then my dev database is going to get full of test data.

If it would help I can supply you with a sample solution.

Thanks for your time and help,

Steve Wood Webmastermedia

Chester
Support Team
Posts: 223
Joined: 15-Jul-2005
# Posted on: 28-Jan-2007 03:08:17   

As Otis says, it's better not to rely on the use of transactions for these kinds of tests. In my opinion your issues are exactly the reason not to do so - it's way too complicated and it therefore IMHO decreases the value of the tests.

I usually create my test entities and after doing so at the PK's of each created entity to a collection that I can then iterate through in teardown to delete all the entities. You'll never have to worry about your test db getting filled up with test data.

Posts: 6
Joined: 25-Jan-2007
# Posted on: 30-Jan-2007 10:23:46   

Thanks to all for your feedback.

We've refactored our tests to manually remove data from the database after the tests complete.

I accept the argument about not using a transactions to manage the data in this way; in the past I'd had experience of using a system that maintained data in a Session-like cache and it was very easy to manage test data in that way.

The challenge, as always, is writing productive code in an elegant way - i.e. in as few lines as possible!

Thanks,

Steve Wood Webmastermedia