new object querying for children....causing lock

Posts   
 
    
peljam
User
Posts: 6
Joined: 28-Jan-2009
# Posted on: 29-Jan-2009 03:20:52   

Let's say I have a CustomerEntity and OrderEntity, and the customer has orders(as children).

If I do this:

CustomerEntity customerEntity= new CustomerEntity(); GlobalTransaction.Add(customerEntity);

//fill in customer data

OrderEntity orderEntity = new OrderEntity();

//fill in order entity data

customerEntity.Order.Add(orderEntity);

When this code is executed and it gets to the last line it performs a query on the database to find all Order entities relating to this Customer entity. However the customer entity is brand new. Why would it need to do this? The query is looking for all orders with a customerID of zero. Technically speaking this record could exist in the database(if the Identity seed for the table started at zero)...even though it should not be related to this customer.

Also I have another strange locking issue. When the last line executes it also causes a database timeout. I have not figured out why. I have a globally running transaction that I add all entities and collections to. However in this case, when I enlist the CustomerEntity in the global transaction like so:

CustomerEntity customerEntity= new CustomerEntity(); GlobalTransaction.Add(customerEntity);

//fill in customer data

etc...

Does this automatically enlist any queries on child lists in this same transaction? If not I have a big problem.

In addition eventually I call:

customerEntity.Save(true)

and I expect that everything will be running in the 'GlobalTransaction' I have created(since I added the parent to it). The documentation does not cover this scenario. However even if it does on the 'Save(true)' I would also hope it does so when these automatic queries are running to find child records.

I am using LLBLGen 2.0.

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 29-Jan-2009 10:58:16   

The related entities are added to their parent's transaction object at the point where you add the parent to the transaction.

So keep the following line as your last one just before you do the save.

GlobalTransaction.Add(customerEntity);

Anyway in general Saving a graph recursively, automatically creates a transaction.

Example from docs (read the comments):

// [C#]
// create a new order and then 2 new order rows. 
// create new order entity. Use data from the object 'customer'
OrderEntity newOrder = new OrderEntity();

// set the customer reference, which will sync FK-PK values.
// (newOrder.CustomerID = customer.CustomerID). You also could have said:
// newOrder.CustomerID = customer.CustomerID;
// or
// newOrder.CustomerID = _someVariable;
newOrder.Customer = customer;

newOrder.EmployeeID = 1;
newOrder.Freight = 10;
newOrder.OrderDate = DateTime.Now.AddDays(-3.0);
newOrder.RequiredDate = DateTime.Now.AddDays(3.0);
newOrder.ShipAddress = customer.Address;
newOrder.ShipCity = customer.City;
newOrder.ShipCountry = customer.Country;
newOrder.ShipName = "The Bounty";
newOrder.ShippedDate = DateTime.Now;
newOrder.ShipRegion = customer.Region;
newOrder.ShipVia = 1;
newOrder.ShipPostalCode = customer.PostalCode;

// Create new order row.
OrderDetailsEntity newOrderRow = new OrderDetailsEntity();
newOrderRow.Discount = 0;
newOrderRow.ProductID = 10;
newOrderRow.Quantity = 200;
newOrderRow.UnitPrice = 31;
// make sure the OrderID fields are synchronized when
// newOrder is saved.
newOrder.OrderDetails.Add(newOrderRow);

// save the new order, recursively. This will first save customer
// if that's changed, then newOrder, then sync newOrder.OrderID with newOrderRow.OrderID
// and then save newOrderRow. The complete Save action is done inside an ADO.NET transaction.
newOrder.Save(true);
peljam
User
Posts: 6
Joined: 28-Jan-2009
# Posted on: 29-Jan-2009 20:27:10   

Thanks for the info. I think I'm understanding how it works more after some testing. I no longer have any locking issues after changing some code around, but I just want to get a confirmation on a few points, as it would still save me a lot of time. You might have already answered these but it would be great if you could just give me a definitive yes/no/explanation type answer to each of the following 3 points:

1) Does recursing into a graph of objects(which automatically queries the database) cause those queries to be attached to the same transaction as the parent? I.e.

//get a customer...

GlobalTransaction.Add(customerEntity);

//This next statement automatically queries the database for all orders for this customer //will this query be part of the GlobalTransaction? This is important to know if I want to //control the islolation level of queries for child objects. customerEntity.Order.Count

2)When saving a graph of objects will all sub-object sql also be part of the parent transaction? The documentation states it will save the entire graph in a ADO.NET transaction, but will this be the same as the parent? I.e.

//get a customer

GlobalTransaction.Add(customerEntity);

customerEntity.Order[0].Description = "new description";

//When this next statement is called will the Order object be saved as part of //GlobalTransaction? Or will it always be in it's own unique ADO.NET transaction customerEntity.Save(true);

3)When you create a new object and then reference child collections from that new object why does it query the database? I.e.

CustomerEntity customerEntity = new CustomerEntity();

//fill customer fields

OrderEntity orderEntity = new OrderEntity();

//fill order fields

GlobalTransaction.Add(customerEntity);

//This next statement executes a query(select .... from order where customerid = 0). Why //does it do this? The customer is new and could not possibly have any orders in the //database. If the answer to point 1 is 'No' then this makes it difficult to use the //self-servicing model with transactions customerEntity.Order.Add(orderEntity);

Anyhow thanks again for your responses. I think I'm starting to understand this better, but if could answer these points it would give me a definite picture of how my code should work...

Thanks!

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 30-Jan-2009 09:49:14   

1) No.

2) Will use it's own unique ADO.NET transaction, unless the customerEntity.Orders were added to the GlobalTransaction before the Save.

3) That's called LazyLoading which is only available in SelfServicing. Related entities are fetched from the database when their corresponding property is accessed.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39859
Joined: 17-Aug-2003
# Posted on: 30-Jan-2009 10:12:34   

v2.5 and 6 have changes which prevent lazy loading queries from being issued when the object is new.

this line: customerEntity.Order.Add(orderEntity);

can be written as: orderEntity.Customer = customerEntity;

which will inplicitly add orderEntity to customerEntity.Orders

this won't trigger lazy loading, so if you want to be on the safe side, use that. simple_smile

Frans Bouma | Lead developer LLBLGen Pro
peljam
User
Posts: 6
Joined: 28-Jan-2009
# Posted on: 02-Feb-2009 19:54:34   

Thanks for the information and the alternatives. It's good that 2.5 fixes the issue that lazy loading causes queries for new objects.

A couple other changes would be make it easier to use the self-servicing template with transactions and help prevent locking issues:

1 - Have lazy loading use the same transaction that is attached to the parent(if there is one).

I.e.

GlobalTransaction.Add(Parent);

//then be able to recurse down into any children all in one transaction Parent.Child[0].ChildChild...etc

2 - Being able to specify a transaction in the constructor of any entity in the self-servicing pattern

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 03-Feb-2009 10:07:46   

1 - Have lazy loading use the same transaction that is attached to the parent(if there is one).

You can't keep a transaction open and waiting for Lazy laoding. As you'd never know when a Lazy Load might occur (and hence the name Lazy).

I'd also recommend avoid fetching entities in Transactions, and only use Transactions in inserts, updates and deletes.