Skip to content
7 changes: 7 additions & 0 deletions src/EFCore.Relational/Update/ModificationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,13 @@ void HandleSharedColumns(
(InternalEntityEntry)currentEntry, currentOwnership)!;
#pragma warning restore EF1001 // Internal EF Core API usage.

// principal entity was replaced with a different derived type (e.g. TPH),
// no need to do any json-specific processing
if (currentEntry == null)
Comment thread
AndriySvyryd marked this conversation as resolved.
Outdated
{
return null;
}

if (processedEntries.Contains(currentEntry))
{
return null;
Comment thread
AndriySvyryd marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,38 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.Property(x => x.Name);
});

modelBuilder.Entity<JsonEntityTphItem>(b =>
{
b.Property(x => x.Id).ValueGeneratedNever();
b.HasMany(x => x.Attributes).WithOne().HasForeignKey(x => x.JsonEntityTphItemId);
});

modelBuilder.Entity<JsonEntityTphItemAttribute>(b =>
{
b.HasKey(x => new { x.JsonEntityTphItemId, x.Key });
b.HasDiscriminator<string>("Discriminator")
.HasValue<JsonEntityTphStringAttribute>("string")
.HasValue<JsonEntityTphLocaleAttribute>("locale-value");
});

modelBuilder.Entity<JsonEntityTphStringAttribute>(b =>
{
b.Property(x => x.Value).HasColumnName("StringValue");
});

modelBuilder.Entity<JsonEntityTphLocaleAttribute>(b =>
{
b.OwnsOne(x => x.Value, bc =>
{
bc.ToJson("LocaleValue");
bc.OwnsMany(x => x.Entries, v =>
{
v.Property(x => x.Locale).IsRequired();
v.Property(x => x.Value);
});
});
});

base.OnModelCreating(modelBuilder, context);
}

Expand All @@ -209,11 +241,13 @@ protected override Task SeedAsync(JsonQueryContext context)
var jsonEntitiesInheritance = JsonQueryData.CreateJsonEntitiesInheritance();
var jsonEntitiesAllTypes = JsonQueryData.CreateJsonEntitiesAllTypes();
var jsonEntitiesConverters = JsonQueryData.CreateJsonEntitiesConverters();
var jsonEntitiesTphItems = JsonQueryData.CreateJsonEntitiesTphItems();

context.JsonEntitiesBasic.AddRange(jsonEntitiesBasic);
context.JsonEntitiesInheritance.AddRange(jsonEntitiesInheritance);
context.JsonEntitiesAllTypes.AddRange(jsonEntitiesAllTypes);
context.JsonEntitiesConverters.AddRange(jsonEntitiesConverters);
context.JsonEntitiesTphItems.AddRange(jsonEntitiesTphItems);

return context.SaveChangesAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3732,6 +3732,34 @@ public virtual Task Replace_json_reference_root_preserves_nested_owned_entities_
Assert.NotNull(result.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf);
});

[ConditionalFact]
public virtual Task Replace_derived_entity_with_json_to_different_derived_type_with_same_key()
=> TestHelpers.ExecuteWithStrategyInTransactionAsync(
CreateContext,
UseTransaction,
async context =>
{
var item = await context.JsonEntitiesTphItems.Include(x => x.Attributes).SingleAsync();

item.Attributes.RemoveAll(attr => attr.Key == "TextValue");
item.Attributes.Add(new JsonEntityTphStringAttribute
{
Key = "TextValue",
Value = "World"
});

ClearLog();
await context.SaveChangesAsync();
},
async context =>
{
var item = await context.JsonEntitiesTphItems.Include(x => x.Attributes).SingleAsync();
var attribute = Assert.Single(item.Attributes);
var stringAttribute = Assert.IsType<JsonEntityTphStringAttribute>(attribute);
Assert.Equal("TextValue", stringAttribute.Key);
Assert.Equal("World", stringAttribute.Value);
});

public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery;

#nullable disable

public class JsonEntityTphItem
{
public int Id { get; set; }
public string Name { get; set; }
public List<JsonEntityTphItemAttribute> Attributes { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery;

#nullable disable

public abstract class JsonEntityTphItemAttribute
{
public int JsonEntityTphItemId { get; set; }
public string Key { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery;

#nullable disable

public class JsonEntityTphLocaleAttribute : JsonEntityTphItemAttribute
{
public JsonOwnedTphLocaleValue Value { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery;

#nullable disable

public class JsonEntityTphStringAttribute : JsonEntityTphItemAttribute
{
public string Value { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery;

#nullable disable

public class JsonOwnedTphLocaleValue
{
public List<JsonOwnedTphLocaleValueEntry> Entries { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery;

#nullable disable

public class JsonOwnedTphLocaleValueEntry
{
public string Locale { get; set; }
public string Value { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class JsonQueryContext(DbContextOptions options) : DbContext(options)
public DbSet<JsonEntityInheritanceBase> JsonEntitiesInheritance { get; set; }
public DbSet<JsonEntityAllTypes> JsonEntitiesAllTypes { get; set; }
public DbSet<JsonEntityConverters> JsonEntitiesConverters { get; set; }
public DbSet<JsonEntityTphItem> JsonEntitiesTphItems { get; set; }

public static Task SeedAsync(JsonQueryContext context)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public JsonQueryData()
JsonEntitiesInheritance = CreateJsonEntitiesInheritance();
JsonEntitiesAllTypes = CreateJsonEntitiesAllTypes();
JsonEntitiesConverters = CreateJsonEntitiesConverters();
JsonEntitiesTphItems = CreateJsonEntitiesTphItems();
}

public IReadOnlyList<EntityBasic> EntitiesBasic { get; }
Expand All @@ -33,6 +34,7 @@ public JsonQueryData()
public IReadOnlyList<JsonEntityInheritanceBase> JsonEntitiesInheritance { get; set; }
public IReadOnlyList<JsonEntityAllTypes> JsonEntitiesAllTypes { get; set; }
public IReadOnlyList<JsonEntityConverters> JsonEntitiesConverters { get; set; }
public IReadOnlyList<JsonEntityTphItem> JsonEntitiesTphItems { get; set; }

public static IReadOnlyList<JsonEntityBasic> CreateJsonEntitiesBasic()
{
Expand Down Expand Up @@ -1470,6 +1472,30 @@ public static IReadOnlyList<JsonEntityConverters> CreateJsonEntitiesConverters()
};
}

public static IReadOnlyList<JsonEntityTphItem> CreateJsonEntitiesTphItems()
=> new List<JsonEntityTphItem>
{
new()
{
Id = 1,
Name = "Product 1",
Attributes =
[
new JsonEntityTphLocaleAttribute
{
Key = "TextValue",
Value = new JsonOwnedTphLocaleValue
{
Entries =
[
new JsonOwnedTphLocaleValueEntry { Locale = "en-US", Value = "Hello" }
]
}
}
]
}
};

public IQueryable<TEntity> Set<TEntity>()
where TEntity : class
{
Expand Down Expand Up @@ -1523,6 +1549,11 @@ public IQueryable<TEntity> Set<TEntity>()
return (IQueryable<TEntity>)JsonEntitiesBasicForCollection.AsQueryable();
}

if (typeof(TEntity) == typeof(JsonEntityTphItem))
{
return (IQueryable<TEntity>)JsonEntitiesTphItems.AsQueryable();
}

throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
});

modelBuilder.Entity<JsonEntityConverters>().OwnsOne(x => x.Reference).ToJson().HasColumnType("json");

modelBuilder.Entity<JsonEntityTphLocaleAttribute>(b =>
{
b.OwnsOne(x => x.Value).ToJson().HasColumnType("json");
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3299,6 +3299,11 @@ FROM [JsonEntitiesBasic] AS [j]
protected override void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();

public override async Task Replace_derived_entity_with_json_to_different_derived_type_with_same_key()
{
await base.Replace_derived_entity_with_json_to_different_derived_type_with_same_key();
}
Comment thread
AndriySvyryd marked this conversation as resolved.

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
Loading