Skip to content

(#360) Convert Add to Replace after conflict #361

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

Merged
merged 1 commit into from
May 23, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using CommunityToolkit.Datasync.Client.Threading;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Net;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json;

Expand All @@ -23,7 +23,7 @@ internal class OperationsQueueManager : IOperationsQueueManager
/// <summary>
/// A lock object for locking against concurrent changes to the queue.
/// </summary>
private readonly object pushlock = new();
private readonly Lock pushlock = new();

/// <summary>
/// The map of valid entities that can be synchronized to the service.
Expand Down Expand Up @@ -67,24 +67,26 @@ internal OperationsQueueManager(OfflineDbContext context)
/// in scope for the operations queue.
/// </summary>
/// <returns>A list of <see cref="EntityEntry"/> values.</returns>
[SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Readability")]
internal List<EntityEntry> GetChangedEntitiesInScope()
=> ChangeTracker.Entries()
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
.Where(e => this._entityMap.ContainsKey(e.Metadata.Name.AsNullableEmptyString()))
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted && this._entityMap.ContainsKey(e.Metadata.Name.AsNullableEmptyString()))
.ToList();

/// <summary>
/// Retrieves the list of synchronizable entities that are available for datasync operations.
/// </summary>
/// <remarks>
/// An entity is "synchronization ready" if:
///
/// <para>An entity is "synchronization ready" if:</para>
/// <para>
/// * It is a property on this context
/// * The property is public and a <see cref="DbSet{TEntity}"/>.
/// * The property does not have a <see cref="DoNotSynchronizeAttribute"/> specified.
/// * The entity type is defined in the model.
/// * The entity type has an Id, UpdatedAt, and Version property (according to the <see cref="EntityResolver"/>).
/// </para>
/// </remarks>
[SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Readability")]
internal Dictionary<string, Type> GetEntityMap(OfflineDbContext context)
{
ArgumentNullException.ThrowIfNull(context);
Expand Down Expand Up @@ -215,11 +217,12 @@ internal IEnumerable<Type> GetSynchronizableEntityTypes(IEnumerable<Type> allowe
/// Determines if the provided property is a synchronizable property.
/// </summary>
/// <remarks>
/// An entity is "synchronization ready" if:
///
/// <para>An entity is "synchronization ready" if:</para>
/// <para>
/// * It is a property on this context
/// * The property is public and a <see cref="DbSet{TEntity}"/>.
/// * The property does not have a <see cref="DoNotSynchronizeAttribute"/> specified.
/// </para>
/// </remarks>
/// <param name="property">The <see cref="PropertyInfo"/> for the property to check.</param>
/// <returns><c>true</c> if the property is synchronizable; <c>false</c> otherwise.</returns>
Expand All @@ -243,6 +246,7 @@ internal bool IsSynchronizationEntity(PropertyInfo property)
/// <param name="pushOptions">The options to use for this push operation.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe.</param>
/// <returns>The results of the push operation (asynchronously)</returns>
[SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Readability")]
internal async Task<PushResult> PushAsync(IEnumerable<Type> entityTypes, PushOptions pushOptions, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(entityTypes);
Expand Down Expand Up @@ -309,6 +313,13 @@ internal async Task<PushResult> PushAsync(IEnumerable<Type> entityTypes, PushOpt

if (resolution.Result is ConflictResolutionResult.Client)
{
// The client entity is the winner, so we need to update the operation and re-queue it.
if (operation.Kind == OperationKind.Add)
{
// The server has an entity and the client is winning, so we need to replace the entity on the server.
operation.Kind = OperationKind.Replace;
}

operation.Item = JsonSerializer.Serialize(resolution.Entity, entityType, DatasyncSerializer.JsonSerializerOptions);
operation.State = OperationState.Pending;
operation.LastAttempt = DateTimeOffset.UtcNow;
Expand Down