Within a TransactionScope, what does calling ToList() for a variable to be used later in the transaction do?

StackOverflow https://stackoverflow.com/questions/23161252

Question

Suppose I create a transaction scope. My primary key is not autogenerated so I must get the latest value from the database and use this value as the PK for the value about to be inserted. This snippit of code is called within the transaction and then stored into the entity. The entity is the inserted and saved into the database.

container.HourlyHealthEntries
                .Where(h => h.Date == healthEntry.Date)
                .Where(h => h.StoreGuid == healthEntry.StoreGuid)
                .OrderBy(h => h.RowIndex)
                .ToList() //we need to materialize the list since LINQ to Entity doesn't translate LastOrDefault
                .LastOrDefault();

I want to know what happens. Is a new transaction created in order to materialize the value into memory and then referenced in the outside transaction? The reason I ask this is because in a multithreaded environment, we are running into deadlocks. I assume it is because the way we have to read and insert into the database. I "fixed" this by removing the ToList and just using OrderByDescending().FirstOrDefault which I assume translates into SQL easier. It may just a speed fix that happens to hide this issue even deeper. Thoughts?

Was it helpful?

Solution

It certainly helps to minimize the time between querying the last value and saving a new record.

You already found that sorting in descending order is more efficient. The next thing to do is only query the Id value, not the entire entity:

container.HourlyHealthEntries
         .Where(h => h.Date == healthEntry.Date)
         .Where(h => h.StoreGuid == healthEntry.StoreGuid)
         .Select(h => h.RowIndex)
         .OrderByDescending(i => i)
         .FirstOrDefault();

Now a minimum amount of data crosses the wire and only an integer is created client-side.

Now immediately use this value to set the PK value, and save.

Enclose this in a TransactionScope with isolationlevel serializable:

var opt = new TransactionOptions();
opt.IsolationLevel = System.Transactions.IsolationLevel.Serializable;
using(var ts = new TransactionScope(TransactionScopeOption.Required, opt))
{
    ...

Now you're only getting conflicts when other users try to modify the records you read in the transaction, but they can still read them.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top