Skip to content

Commit 49bd9d1

Browse files
(#211) Client: Fix wrong EntityType in OperationsQueue when using Lazy Loading Proxies (#212)
* add test * replace GetType() calls with Metadata CLR type * impove test --------- Co-authored-by: Adrian Hall <photoadrian@outlook.com>
1 parent c860e96 commit 49bd9d1

File tree

3 files changed

+60
-8
lines changed

3 files changed

+60
-8
lines changed

src/CommunityToolkit.Datasync.Client/Offline/OperationsQueue/OperationsQueueManager.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ internal Dictionary<string, Type> GetEntityMap(OfflineDbContext context)
114114
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe.</param>
115115
/// <returns>The operation entity or null if one does not exist.</returns>
116116
/// <exception cref="DatasyncException">Thrown if the entity ID of the provided entity is invalid.</exception>
117-
internal async ValueTask<DatasyncOperation?> GetExistingOperationAsync(object entity, CancellationToken cancellationToken = default)
117+
internal async ValueTask<DatasyncOperation?> GetExistingOperationAsync(EntityEntry entityEntry, CancellationToken cancellationToken = default)
118118
{
119-
Type entityType = entity.GetType();
120-
EntityMetadata metadata = EntityResolver.GetEntityMetadata(entity, entityType);
119+
Type entityType = entityEntry.Metadata.ClrType;
120+
EntityMetadata metadata = EntityResolver.GetEntityMetadata(entityEntry.Entity, entityType);
121121
if (!EntityResolver.EntityIdIsValid(metadata.Id))
122122
{
123123
throw new DatasyncException($"Entity ID for type {entityType.FullName} is invalid.");
@@ -143,7 +143,7 @@ internal Task<long> GetLastSequenceIdAsync(CancellationToken cancellationToken =
143143
/// <returns>The operation definition.</returns>
144144
internal DatasyncOperation GetOperationForChangedEntity(EntityEntry entry)
145145
{
146-
Type entityType = entry.Entity.GetType();
146+
Type entityType = entry.Metadata.ClrType;
147147
EntityMetadata metadata = EntityResolver.GetEntityMetadata(entry.Entity, entityType);
148148
if (!EntityResolver.EntityIdIsValid(metadata.Id))
149149
{
@@ -432,7 +432,7 @@ public async Task UpdateOperationsQueueAsync(CancellationToken cancellationToken
432432
foreach (EntityEntry entry in entitiesInScope)
433433
{
434434
DatasyncOperation newOperation = GetOperationForChangedEntity(entry);
435-
DatasyncOperation? existingOperation = await GetExistingOperationAsync(entry.Entity, cancellationToken).ConfigureAwait(false);
435+
DatasyncOperation? existingOperation = await GetExistingOperationAsync(entry, cancellationToken).ConfigureAwait(false);
436436
if (existingOperation is null)
437437
{
438438
newOperation.Sequence = Interlocked.Increment(ref sequenceId);

tests/CommunityToolkit.Datasync.Client.Test/Offline/Helpers/BaseTest.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,22 @@ public abstract class BaseTest
2525
/// </summary>
2626
protected static TestDbContext CreateContext(Action<DbContextOptionsBuilder<TestDbContext>> configureOptions = null)
2727
{
28-
SqliteConnection connection = new("Data Source=:memory:");
29-
connection.Open();
28+
SqliteConnection connection = CreateAndOpenConnection();
29+
DbContextOptionsBuilder<TestDbContext> optionsBuilder = new DbContextOptionsBuilder<TestDbContext>()
30+
.UseSqlite(connection);
31+
configureOptions?.Invoke(optionsBuilder);
32+
TestDbContext context = new(optionsBuilder.Options) { Connection = connection };
33+
34+
// Ensure the database is created.
35+
context.Database.EnsureCreated();
36+
return context;
37+
}
38+
39+
/// <summary>
40+
/// Creates a version of the TestDbContext backed by the specified SQLite connection.
41+
/// </summary>
42+
protected static TestDbContext CreateContext(SqliteConnection connection, Action<DbContextOptionsBuilder<TestDbContext>> configureOptions = null)
43+
{
3044
DbContextOptionsBuilder<TestDbContext> optionsBuilder = new DbContextOptionsBuilder<TestDbContext>()
3145
.UseSqlite(connection);
3246
configureOptions?.Invoke(optionsBuilder);
@@ -37,6 +51,16 @@ protected static TestDbContext CreateContext(Action<DbContextOptionsBuilder<Test
3751
return context;
3852
}
3953

54+
/// <summary>
55+
/// Creates and opens an in-memory SQLite database connection.
56+
/// </summary>
57+
protected static SqliteConnection CreateAndOpenConnection()
58+
{
59+
SqliteConnection connection = new("Data Source=:memory:");
60+
connection.Open();
61+
return connection;
62+
}
63+
4064
/// <summary>
4165
/// Creates a response message based on code and content.
4266
/// </summary>

tests/CommunityToolkit.Datasync.Client.Test/Offline/OperationsQueueManager_Tests.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using CommunityToolkit.Datasync.TestCommon.Databases;
1010
using Microsoft.EntityFrameworkCore;
1111
using System.Net;
12+
using Microsoft.Data.Sqlite;
1213
using TestData = CommunityToolkit.Datasync.TestCommon.TestData;
1314

1415
namespace CommunityToolkit.Datasync.Client.Test.Offline;
@@ -39,7 +40,7 @@ public void GetEntityMap_Works()
3940
public async Task GetExistingOperationAsync_InvalidId_Throws()
4041
{
4142
ClientMovie movie = new() { Id = "###" };
42-
Func<Task> act = async () => _ = await queueManager.GetExistingOperationAsync(movie);
43+
Func<Task> act = async () => _ = await queueManager.GetExistingOperationAsync(context.Entry(movie));
4344
await act.Should().ThrowAsync<DatasyncException>();
4445
}
4546
#endregion
@@ -482,5 +483,32 @@ public async Task LLP_PushAsync_Replacement_Works()
482483

483484
llpContext.DatasyncOperationsQueue.Should().BeEmpty();
484485
}
486+
487+
[Fact]
488+
public async Task LLP_ModifyAfterInsertInNewContext_NoPush_ShouldUpdateOperationsQueue()
489+
{
490+
await using SqliteConnection connection = CreateAndOpenConnection();
491+
string id = Guid.NewGuid().ToString("N");
492+
await using (TestDbContext llpContext = CreateContext(connection, x => x.UseLazyLoadingProxies()))
493+
{
494+
ClientMovie clientMovie = new(TestData.Movies.MovieList[0].Title) { Id = id };
495+
llpContext.Movies.Add(clientMovie);
496+
llpContext.SaveChanges();
497+
}
498+
499+
await using TestDbContext newLlpContext = CreateContext(connection, x => x.UseLazyLoadingProxies());
500+
501+
ClientMovie storedClientMovie = newLlpContext.Movies.First(m => m.Id == id);
502+
503+
// ensure that it is a lazy loading proxy and not exactly a ClientMovie
504+
storedClientMovie.GetType().Should().NotBe(typeof(ClientMovie))
505+
.And.Subject.Namespace.Should().Be("Castle.Proxies");
506+
507+
storedClientMovie.Title = TestData.Movies.MovieList[1].Title;
508+
newLlpContext.SaveChanges();
509+
510+
newLlpContext.DatasyncOperationsQueue.Should().ContainSingle(op => op.ItemId == id)
511+
.Which.EntityType.Should().NotContain("Castle.Proxies");
512+
}
485513
#endregion
486514
}

0 commit comments

Comments
 (0)