Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concurrency check is missing in Updates #469

Closed
DevInstinct opened this issue Feb 18, 2021 · 6 comments
Closed

Concurrency check is missing in Updates #469

DevInstinct opened this issue Feb 18, 2021 · 6 comments

Comments

@DevInstinct
Copy link

This is a duplicate of #209, which has been unfortunately closed.

Entity Framework generates a WHERE close on updates to check concurrent updates for each entity that has a concurrency token (timestamp). The bulk updates don't generate those WHERE clauses and skip EF concurrency checks.

Any plan to add support for concurrency checks?

@borisdj
Copy link
Owner

borisdj commented Mar 9, 2021

Will consider it.
So when concurrency conflict is found the operation should abort?

@DevInstinct
Copy link
Author

My suggestion would be that the operation should throw a concurrency exception.

@borisdj
Copy link
Owner

borisdj commented Mar 21, 2021

Support added with latest version 3.4.7+.
For BulkUpdate set
bulkConfig = new BulkConfig { SetOutputIdentity = true, DoNotUpdateIfTimeStampChanged = true };
It works in a way that those rows with concurrency conflict, meaning TimeStamp column is changed since read, will not be updated and their entities will be loaded into BulkConfig in TimeStampInfo object with properties:
{ int NumberOfSkippedForUpdate, List<object> EntitiesOutput }
After reading them, depending on our need, we can either leave them skipped, update them again, throw exception or even rollback entire Update.
Example in the Test:

private void TimeStampTest(DbServer databaseType)
{
ContextUtil.DbServer = databaseType;
using (var context = new TestContext(ContextUtil.GetOptions()))
{
context.Truncate<File>();
var entities = new List<File>();
for (int i = 1; i <= EntitiesNumber; i++)
{
var entity = new File
{
Data = "Some data " + i
};
entities.Add(entity);
}
context.BulkInsert(entities, bulkAction => bulkAction.SetOutputIdentity = true); // example of setting BulkConfig with Action argument
// For testing concurrency conflict (UPDATE changes RowVersion which is TimeStamp column)
context.Database.ExecuteSqlRaw("UPDATE dbo.[File] SET Data = 'Some data 1 PRE CHANGE' WHERE [Id] = 1;");
var entitiesToUpdate = entities.Take(10).ToList();
foreach (var entityToUpdate in entitiesToUpdate)
{
entityToUpdate.Data += " UPDATED";
}
using (var transaction = context.Database.BeginTransaction())
{
var bulkConfig = new BulkConfig { SetOutputIdentity = true, DoNotUpdateIfTimeStampChanged = true };
context.BulkUpdate(entitiesToUpdate, bulkConfig);
var list = bulkConfig.TimeStampInfo?.EntitiesOutput.Cast<File>().ToList();
Assert.Equal(9, list.Count());
Assert.Equal(1, bulkConfig.TimeStampInfo.NumberOfSkippedForUpdate);
if (bulkConfig.TimeStampInfo?.NumberOfSkippedForUpdate > 0)
{
transaction.Rollback();
}
else
{
transaction.Commit();
}
}
}
}

@defunky
Copy link

defunky commented Jun 25, 2022

@borisdj I can't seem to get this to work (I'm using upsert), TimeStampInfo seems to always be null. I've defined the RowVersion as RowVersion type in the table? Does the library support optimistic locking via ConcurrencyCheck attribute as well?

@oresttkachukd
Copy link

Tried both, always receive TimeStampInfo to be null:

  • type: "timestamp", rowVersion: true, nullable: false
  • type: "rowversion", rowVersion: true, nullable: false

@oresttkachukd
Copy link

@defunky in case it is still valid - adding SetOutputIdentity = true fixes the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants