-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
[CosmosDB] Handling Concurrency when owned entities are updated externally #28166
Comments
#13559 will probably help you, but meanwhile you can recurse through the owned types like this: private async static Task MergeDatabaseValuesAsync(EntityEntry entry, EntityEntry databaseEntry)
{
if (entry == null
|| databaseEntry == null)
{
return;
}
// Refresh original values
entry.OriginalValues.SetValues(databaseEntry.OriginalValues);
foreach (var propertyEntry in entry.Properties)
{
if (!propertyEntry.IsModified)
{
// Reset property to the value currently in DB because we've not changed it locally
propertyEntry.CurrentValue = propertyEntry.OriginalValue;
}
}
foreach (var reference in entry.References)
{
await MergeDatabaseValuesAsync(reference.TargetEntry, databaseEntry.Reference(reference.Metadata).TargetEntry);
}
foreach (var collection in entry.Collections)
{
var databaseCollection = databaseEntry.Collection(collection.Metadata);
// Assuming the order and length have not changed
var iterator1 = collection.CurrentValue.GetEnumerator();
var iterator2 = databaseCollection.CurrentValue.GetEnumerator();
while (iterator1.MoveNext() && iterator2.MoveNext())
{
await MergeDatabaseValuesAsync(
collection.FindEntry(iterator1.Current), databaseCollection.FindEntry(iterator2.Current));
}
}
} To use it you'd need to create another context instance: using (var context2 = contextFactory.CreateDbContext())
{
var databaseEntity = await context2.FindAsync<Book>(entry.Property<string>("id").CurrentValue);
await MergeDatabaseValuesAsync(entry, context2.Entry(databaseEntity));
} |
You have to reverse the proposition in order for it to work (because if you do "entry.OriginalValues.SetValues(databaseEntry.OriginalValues)", properties modified inside the database should be marked as modified, so the locally values will be applied). Proposition : private async static Task MergeDatabaseValuesAsync(EntityEntry entry, EntityEntry databaseEntry)
{
if (entry == null
|| databaseEntry == null)
{
return;
}
foreach (var propertyEntry in entry.Properties)
{
if (propertyEntry.IsModified)
{
// Set the new property value for the database entry, because it has been changed locally
databaseEntry.Properties.Single(x => x.Metadata.Name == propertyEntry.Metadata.Name).CurrentValue = propertyEntry.CurrentValue;
}
}
foreach (var reference in entry.References)
{
await MergeDatabaseValuesAsync(reference.TargetEntry, databaseEntry.Reference(reference.Metadata).TargetEntry);
}
foreach (var collection in entry.Collections)
{
var databaseCollection = databaseEntry.Collection(collection.Metadata);
// Assuming the order and length have not changed
var iterator1 = collection.CurrentValue.GetEnumerator();
var iterator2 = databaseCollection.CurrentValue.GetEnumerator();
while (iterator1.MoveNext() && iterator2.MoveNext())
{
await MergeDatabaseValuesAsync(
collection.FindEntry(iterator1.Current), databaseCollection.FindEntry(iterator2.Current));
}
}
} |
Issue we're having
Since Cosmos updates are entire documents right now, we've an object (typically under 5kb) to represent a device. Multiple services interact with the database using EF at the same time. What we're seeing is that when we process messages from a device, the updates to the database sometimes overwrite data on the device which has been set via our API.
e.g.
We've turned to using ETag concurrency to handle this but due to the nature of how the updates are done, we'd like to re-apply ONLY the changes we've made since the record has been updated in Cosmos but are having a lot of issues with owned/embedded entities causing issues.
Is there anything that we can do to better handle this scenario?
For example can we can we GetDatabaseValues on the owned entities and do similar processing on those inside of
SaveChangesAndHandleConcurrency
in the code example? We've tried to take inspiration from the code example here.Fundamentally, the underlying problem is that Cosmos EF does not allow a model configuration whereby owned entities simply remain as objects on their owning type which are tracked as a single object instead of being decomposed into separate tracked objects.
Code example
Include provider and version information
EF Core version: Tested using 6.0.5 and 7.0.0-preview.4.22229.2
Database provider: Microsoft.EntityFrameworkCore.Cosmos
Target framework: .NET 6.0
Operating system: Windows 11
IDE: JetBrains Rider 2021.3.2
The text was updated successfully, but these errors were encountered: