diff --git a/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs index fd7c02e182d..aba843e795a 100644 --- a/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs @@ -128,6 +128,9 @@ public virtual Expression BindProperty([NotNull] IProperty property, bool client } if (!clientEval + // TODO: Remove once __jObject is translated to the access root in a better fashion and + // would not otherwise be found to be non-translatable. See issues #17670 and #14121. + && property.Name != EntityFrameworkCore.Metadata.Conventions.StoreKeyConvention.JObjectPropertyName && expression.Name.Length == 0) { // Non-persisted property can't be translated diff --git a/src/EFCore.Cosmos/Query/Internal/KeyAccessExpression.cs b/src/EFCore.Cosmos/Query/Internal/KeyAccessExpression.cs index ff6c3c52bcc..097af897885 100644 --- a/src/EFCore.Cosmos/Query/Internal/KeyAccessExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/KeyAccessExpression.cs @@ -101,7 +101,11 @@ protected override void Print(ExpressionPrinter expressionPrinter) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public override string ToString() => $"{AccessExpression}[\"{Name}\"]"; + public override string ToString() => Name?.Length > 0 + ? $"{AccessExpression}[\"{Name}\"]" + // TODO: Remove once __jObject is translated to the access root in a better fashion. + // See issue #17670 and related issue #14121. + : $"{AccessExpression}"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs index 8ace7c1eab1..07fa2bada71 100644 --- a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs +++ b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs @@ -3,9 +3,13 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Utilities; using Newtonsoft.Json.Linq; @@ -33,7 +37,8 @@ public CosmosTypeMappingSource([NotNull] TypeMappingSourceDependencies dependenc _clrTypeMappings = new Dictionary { - { typeof(byte[]), new CosmosTypeMapping(typeof(byte[]), keyComparer: new ArrayStructuralComparer()) } + { typeof(byte[]), new CosmosTypeMapping(typeof(byte[]), keyComparer: new ArrayStructuralComparer()) }, + { typeof(JObject), new CosmosTypeMapping(typeof(JObject)) } }; } diff --git a/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs b/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs new file mode 100644 index 00000000000..9534df5beaa --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/ReloadTest.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; +using Newtonsoft.Json.Linq; + +namespace Microsoft.EntityFrameworkCore.Cosmos +{ + public class ReloadTest + { + public static IEnumerable IsAsyncData = new[] + { + new object[] { true }, + new object[] { false } + }; + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public async Task Entity_reference_can_be_reloaded(bool async) + { + await using var testDatabase = CosmosTestStore.CreateInitialized("ReloadTest"); + + using var context = new ReloadTestContext(testDatabase); + await context.Database.EnsureCreatedAsync(); + + var entry = context.Add(new Item { Id = 1337 }); + + await context.SaveChangesAsync(); + + var itemJson = entry.Property("__jObject").CurrentValue; + itemJson["unmapped"] = 2; + + if (async) + { + await entry.ReloadAsync(); + } + else + { + entry.Reload(); + } + + itemJson = entry.Property("__jObject").CurrentValue; + Assert.Null(itemJson["unmapped"]); + } + + public class ReloadTestContext : DbContext + { + private readonly string _connectionUri; + private readonly string _authToken; + private readonly string _name; + + public ReloadTestContext(CosmosTestStore testStore) + { + _connectionUri = testStore.ConnectionUri; + _authToken = testStore.AuthToken; + _name = testStore.Name; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .UseCosmos( + _connectionUri, + _authToken, + _name, + b => b.ApplyConfiguration()); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + } + + public DbSet Items { get; set; } + } + + public class Item + { + public int Id { get; set; } + } + } +}