Rollback after Insert

Posts   
 
    
Evan
User
Posts: 67
Joined: 02-May-2012
# Posted on: 16-Sep-2014 22:50:14   

I wrote a long post the other day and I guess the site had an error posting it.

I ran into an issue where I'm creating an entity and tying it to other, existing entities, then rolling back. If I do a recursive adapter.Save later on some other entity, this new entity gets saved to the database. This seems to be true even if it's through 5 other relationships or if I create another, new entity of the same type that has a common relation.

Is there a recommended way of handling this? I went through and made sure I wasn't doing any recursive saves, but is there some other way to get rid of this entity? I tried unlinking the new entity to other entities, but that didn't work for me either. It was also pretty awkward to code not really knowing where the error would occur that would cause the transaction would rollback.

For my current solution, I'm nervous that someone might make changes without realizing the danger of a recursive save. They may add a recursive save(even in some shared code) that will cause a dupe to get inserted.

I briefly looked at context and Unit Of Work, but couldn't tell if they'd help me out here or not. Thanks, --Evan

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 17-Sep-2014 07:43:35   

Please post a tiny code that reproduces your problem. Like doing a Northwind example we can reproduce over here.

What is your LLBLGen version and runtime library version? (http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=810)

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39765
Joined: 17-Aug-2003
# Posted on: 17-Sep-2014 09:59:29   

Indeed an example of what you had might be best. About the site issues: the ISP which hosts our VM running our sites had massive problems last week on their VM platform, which are sorted now. Sorry for the problems posting after spending a lot of time writing the post!

Frans Bouma | Lead developer LLBLGen Pro
Evan
User
Posts: 67
Joined: 02-May-2012
# Posted on: 22-Sep-2014 07:14:16   

OK, so this is pretty close to my scenario. I create an Order Details row, then rollback, then create another one tied to the same order. Both get created. There's some sql at the end to view the rows and wipe them out.
The following all seem to fix the issue: (in my real-world scenario I didn't get #2 to work for some reason) 1. Create the relationship to order using the OrderId. 2. Set the relationship to Order back to null. 3. Specifically use a non-recursive save.

I've included commented lines of code for the fixes listed above.

using (DataAccessAdapter adapter = new DataAccessAdapter())
            {
                adapter.StartTransaction(IsolationLevel.Serializable, "Test Rollback");

                LinqMetaData md = new LinqMetaData(adapter);

                var order = md.Order.First(o => o.OrderId == 10248);
                var product1 = md.Product.First(p => p.ProductId == 1);
                var product2 = md.Product.First(p => p.ProductId == 2);

                OrderDetailEntity orderDetail1 = new OrderDetailEntity(){
                    Order = order,
                    //OrderId = order.OrderId,
                    Product = product1,
                    UnitPrice = 1,
                    Quantity = 1,
                    Discount = .1f
                };

                adapter.SaveEntity(orderDetail1);

                adapter.Rollback();

                //orderDetail1.Order = null;

                adapter.StartTransaction(IsolationLevel.Serializable, "Test Commit");

                order = md.Order.First(o => o.OrderId == 10248);

                OrderDetailEntity orderDetail2 = new OrderDetailEntity()
                {
                    Order = order,
                    Product = product2,
                    UnitPrice = 2,
                    Quantity = 2,
                    Discount = .2f
                };

                adapter.SaveEntity(orderDetail2);
                //adapter.SaveEntity(orderDetail2, false, false);

                adapter.Commit();
            }

select * from [Order Details] where OrderId=10248 delete from [Order Details] where OrderId=10248 and ProductId in (1, 2)

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 24-Sep-2014 08:35:01   

Using your example (as-is), this is the SQL I got:

SELECT TOP(1) [LPA_L1].[CustomerID] AS [CustomerId], ...
FROM   [Northwind].[dbo].[Orders] [LPA_L1]
WHERE  ((([LPA_L1].[OrderID] = 1))) 

SELECT TOP(1) [LPA_L1].[CategoryID] AS [CategoryId], ...
FROM   [Northwind].[dbo].[Products] [LPA_L1]
WHERE  ((([LPA_L1].[ProductID] = 2))) 

SELECT TOP(1) [LPA_L1].[CustomerID] AS [CustomerId], ...
FROM   [Northwind].[dbo].[Orders] [LPA_L1]
WHERE  ((([LPA_L1].[OrderID] = 10248))) 


INSERT INTO [Northwind].[dbo].[Order Details]
            ([Discount],
             [OrderID],
             [ProductID],
             [Quantity],
             [UnitPrice])
VALUES    (1,
             1,
             1,
             1,
             1) 

ROLLBACK


SELECT TOP(1) [LPA_L1].[CustomerID] AS [CustomerId], ...
FROM   [Northwind].[dbo].[Orders] [LPA_L1]
WHERE  ((([LPA_L1].[OrderID] = 10248))) 

INSERT INTO [Northwind].[dbo].[Order Details]
            ([Discount],
             [OrderID],
             [ProductID],
             [Quantity],
             [UnitPrice])
VALUES    (2,
             2,
             2,
             2,
             2) 

COMMIT

Repro code:

adapter.StartTransaction(IsolationLevel.Serializable, "Test Rollback");
LinqMetaData md = new LinqMetaData(adapter);
                
var product1 = md.Product.First(p => p.ProductId == 1);
var product2 = md.Product.First(p => p.ProductId == 2);
var order = md.Order.First(o => o.OrderId == 10248);
OrderDetailEntity orderDetail1 = new OrderDetailEntity()
{
    Order = order,
    Product = product1,
    UnitPrice = 1,
    Quantity = 1,
    Discount = .1f
};

adapter.SaveEntity(orderDetail1);
adapter.Rollback();


adapter.StartTransaction(IsolationLevel.Serializable, "Test Commit");
order = md.Order.First(o => o.OrderId == 10248);
OrderDetailEntity orderDetail2 = new OrderDetailEntity()
{
    Order = order,
    Product = product2,
    UnitPrice = 2,
    Quantity = 2,
    Discount = .2f
};

adapter.SaveEntity(orderDetail2);
adapter.Commit();

Which means that it works as expected. Please check your real scenario to see what exactly is missing. In this case, creating the order again, unlink the previous detail:

order = md.Order.First(o => o.OrderId == 10248);

In your real code, you might need to do that or unlink it manually, setting it to null, as your commented line:

orderDetail1.Order = null;

If you are getting different results in this test code, please give us more information. I tested it with LLBLGen v4.2. What is your LLBLGen version and runtime library version? (http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=7718 )

David Elizondo | LLBLGen Support Team
Evan
User
Posts: 67
Joined: 02-May-2012
# Posted on: 24-Sep-2014 16:02:50   

You are correct in what's happening. I think it's just more difficult to implement in my real world scenario. The Entity I'm creating is related to many more entities. Also, my transaction could fail in a number of different places in the code making it difficult to know when I need to unlink the entity from all its relationships. Also, my rollback call is 1 or 2 levels up in the call stack from where the entity is created.

Seems to me the safest option is to create the associations using the foreign key id fields. Then, cleanup wouldn't be so messy. Still, if someone wasn't aware of this nuance, then things could go terribly awry. Only slightly better than making sure none of the saves are recursive.

Is there any other option? Is there some way to clear out all the entities related to the adapter and start from scratch? Is there some way to revert the in-memory changes or force a recursive reload from the database?

Walaa avatar
Walaa
Support Team
Posts: 14986
Joined: 21-Aug-2005
# Posted on: 25-Sep-2014 19:32:34   

I believe setting the entity to null, in any of its references should sort this out.

Evan
User
Posts: 67
Joined: 02-May-2012
# Posted on: 03-Oct-2014 20:07:33   

Seems like this could be a lot more work and very error prone. What if you have a transaction such as in an import process that creates 10 different entity types and involves many different methods. You'd need to be certain to put every entity you've created somewhere so that you can access them when you rollback. You'd have to create the code for removing every association for every entity type. You have to be careful when you're maintaining the code as well. If you add an relationship and forget to add the code that removes your new relationship then your rollback will not work correctly.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Oct-2014 08:31:19   

Evan wrote:

Seems like this could be a lot more work and very error prone. What if you have a transaction such as in an import process that creates 10 different entity types and involves many different methods. You'd need to be certain to put every entity you've created somewhere so that you can access them when you rollback. You'd have to create the code for removing every association for every entity type. You have to be careful when you're maintaining the code as well. If you add an relationship and forget to add the code that removes your new relationship then your rollback will not work correctly.

Yes, the developer has to know how the framework works. Sometimes this behavior (links, references, update the graph, etc) is how the developer wants things, as it's very common to make changes to a graph and then update all the dirty ones. You just need to know where it's more convenient to use FK fields vs. navigators, or whether to save single or save recursive.

There is an LLBLGen's feature: DatraScopes. Please take a look at it to see if it could help you with your scenario. There is an example solution that you can obtain from the LLBLGen site.

David Elizondo | LLBLGen Support Team