diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 822521d7ede..853f12c00d4 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,58 +1,58 @@ - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be - + https://github.com/dotnet/runtime - 3c48925a6c1ab31865b4438a6cb88242d1a8fe4d + 1222f14d02818cfab597fa2a17c8664fe6cb39be diff --git a/eng/Versions.props b/eng/Versions.props index c777ced56d2..0dcfaa3f653 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -16,19 +16,19 @@ False - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 - 8.0.0-rc.2.23431.9 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 + 8.0.0-rc.2.23456.8 8.0.0-beta.23451.1 diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index b6747539b35..aafeac6f7f8 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -61,6 +61,12 @@ public static class RelationalEntityTypeExtensions return entityType.GetRootType().GetTableName(); } + if (entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy + && !entityType.ClrType.IsInstantiable()) + { + return null; + } + var ownership = entityType.FindOwnership(); if (ownership != null && (ownership.IsUnique || entityType.IsMappedToJson())) @@ -81,12 +87,6 @@ public static class RelationalEntityTypeExtensions : $"{ownership.PrincipalToDependent.Name}_{name}"; } - if (entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy - && !entityType.ClrType.IsInstantiable()) - { - return null; - } - return truncate ? Uniquifier.Truncate(name, entityType.Model.GetMaxIdentifierLength()) : name; diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 187f3297208..157dc23d827 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -1145,7 +1145,7 @@ public static void SetIsFixedLength(this IMutableProperty property, bool? fixedL /// if the mapped column is nullable; otherwise. public static bool IsColumnNullable(this IReadOnlyProperty property) => property.IsNullable - || (property.DeclaringType is IReadOnlyEntityType entityType + || (property.DeclaringType.ContainingEntityType is IReadOnlyEntityType entityType && entityType.BaseType != null && entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy); diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 29b4c1cab2f..84ce793fc1b 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -1521,6 +1521,39 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression) //sometimes we have shadow value buffer and sometimes not, but type initializer always comes last switch (body.Expressions[^1]) { + case UnaryExpression { Operand: BlockExpression innerBlock } jsonEntityTypeInitializerUnary + when jsonEntityTypeInitializerUnary.NodeType is ExpressionType.Convert or ExpressionType.ConvertChecked: + { + // in case of proxies, the entity initializer block is wrapped around Convert node + // that converts from the proxy type to the actual entity type. + // We normalize that into a block by pushing the convert inside the inner block. Rather than: + // + // return (MyEntity) + // { + // ProxyEntity instance; + // (...) + // return instance; + // } + // + // we produce: + // return + // { + // ProxyEntity instance; + // MyEntity actualInstance; + // (...) + // actualInstance = (MyEntity)instance; + // return actualInstance; + // } + var newVariables = innerBlock.Variables.ToList(); + var proxyConversionVariable = Variable(jsonEntityTypeInitializerUnary.Type); + newVariables.Add(proxyConversionVariable); + var newExpressions = innerBlock.Expressions.ToList()[..^1]; + newExpressions.Add(Assign(proxyConversionVariable, jsonEntityTypeInitializerUnary.Update(innerBlock.Expressions[^1]))); + newExpressions.Add(proxyConversionVariable); + jsonEntityTypeInitializerBlock = Block(newVariables, newExpressions); + break; + } + case BlockExpression b: jsonEntityTypeInitializerBlock = b; break; @@ -1976,9 +2009,8 @@ protected override Expression VisitBinary(BinaryExpression node) var currentVariable = Variable(parameter!.Type); return Block( new[] { currentVariable }, - Assign( - currentVariable, - MakeMemberAccess(_instance, property.GetMemberInfo(forMaterialization: true, forSet: false))), + MakeMemberAccess(_instance, property.GetMemberInfo(forMaterialization: true, forSet: false)) + .Assign(currentVariable), IfThenElse( OrElse( ReferenceEqual(currentVariable, Constant(null)), @@ -1991,7 +2023,11 @@ protected override Expression VisitBinary(BinaryExpression node) )); } - return MakeBinary(node.NodeType, Visit(node.Left), parameter!); + var visitedLeft = Visit(node.Left); + return node.NodeType == ExpressionType.Assign + && visitedLeft is MemberExpression memberExpression + ? memberExpression.Assign(parameter!) + : MakeBinary(node.NodeType, visitedLeft, parameter!); } return base.VisitBinary(node); diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs index 360cd51c519..99652dafaff 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryAdHocTestBase.cs @@ -704,6 +704,112 @@ public MyJsonEntityShadowPropertiesWithCtor(string name) #endregion + #region LazyLoadingProxies + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Project_proxies_entity_with_json(bool async) + { + var contextFactory = await InitializeAsync( + seed: SeedLazyLoadingProxies, + onConfiguring: OnConfiguringLazyLoadingProxies, + addServices: AddServicesLazyLoadingProxies); + + using (var context = contextFactory.CreateContext()) + { + var query = context.Entities; + + var result = async + ? await query.ToListAsync() + : query.ToList(); + + Assert.Equal(2, result.Count); + } + } + + protected void OnConfiguringLazyLoadingProxies(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseLazyLoadingProxies(); + + protected void AddServicesLazyLoadingProxies(IServiceCollection addServices) + => addServices.AddEntityFrameworkProxies(); + + private void SeedLazyLoadingProxies(MyContextLazyLoadingProxies ctx) + { + var r1 = new MyJsonEntityLazyLoadingProxiesWithCtor("r1", 1); + var c11 = new MyJsonEntityLazyLoadingProxies { Name = "c11", Number = 11 }; + var c12 = new MyJsonEntityLazyLoadingProxies { Name = "c12", Number = 12 }; + var c13 = new MyJsonEntityLazyLoadingProxies { Name = "c13", Number = 13 }; + + var r2 = new MyJsonEntityLazyLoadingProxiesWithCtor("r2", 2); + var c21 = new MyJsonEntityLazyLoadingProxies { Name = "c21", Number = 21 }; + var c22 = new MyJsonEntityLazyLoadingProxies { Name = "c22", Number = 22 }; + + var e1 = new MyEntityLazyLoadingProxies + { + Id = 1, + Name = "e1", + Reference = r1, + Collection = new List { c11, c12, c13 } + }; + + var e2 = new MyEntityLazyLoadingProxies + { + Id = 2, + Name = "e2", + Reference = r2, + Collection = new List { c21, c22 } + }; + + ctx.Entities.AddRange(e1, e2); + ctx.SaveChanges(); + } + + protected class MyContextLazyLoadingProxies : DbContext + { + public MyContextLazyLoadingProxies(DbContextOptions options) + : base(options) + { + } + + public DbSet Entities { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Reference, b => b.ToJson()); + modelBuilder.Entity().OwnsMany(x => x.Collection, b => b.ToJson()); + } + } + + public class MyEntityLazyLoadingProxies + { + public int Id { get; set; } + public string Name { get; set; } + + public virtual MyJsonEntityLazyLoadingProxiesWithCtor Reference { get; set; } + public virtual List Collection { get; set; } + } + + public class MyJsonEntityLazyLoadingProxiesWithCtor + { + public MyJsonEntityLazyLoadingProxiesWithCtor(string name, int number) + { + Name = name; + Number = number; + } + + public string Name { get; set; } + public int Number { get; set; } + } + + public class MyJsonEntityLazyLoadingProxies + { + public string Name { get; set; } + public int Number { get; set; } + } + + #endregion + protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs index 7fa889511ef..98a3c058f0c 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs @@ -471,7 +471,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con { modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); - modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity( + b => + { + b.Property(x => x.Id).ValueGeneratedNever(); + b.Property(x => x.Name); + }); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); modelBuilder.Entity().OwnsOne( x => x.OwnedReferenceRoot, b => diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityAllTypes.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityAllTypes.cs index fb821d4989d..4e8ae9199b0 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityAllTypes.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityAllTypes.cs @@ -19,11 +19,11 @@ public class JsonEntityAllTypes private Collection _testNullableEnumWithIntConverterCollectionX = new() { JsonEnum.Three }; public int Id { get; set; } - public JsonOwnedAllTypes Reference { get; set; } - public List Collection { get; set; } + public JsonOwnedAllTypes Reference { get; init; } + public List Collection { get; init; } - public string[] TestDefaultStringCollection { get; set; } - public List TestMaxLengthStringCollection { get; set; } + public string[] TestDefaultStringCollection { get; init; } + public List TestMaxLengthStringCollection { get; init; } public IList TestInt16Collection { get; set; } public int[] TestInt32Collection { get; set; } = Array.Empty(); diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityBasicForCollection.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityBasicForCollection.cs index d1b4c7d1c8a..7459c64ac34 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityBasicForCollection.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityBasicForCollection.cs @@ -5,8 +5,8 @@ namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery; public class JsonEntityBasicForCollection { - public int Id { get; set; } - public string Name { get; set; } + public int Id { get; init; } + public string Name { get; init; } public int? ParentId { get; set; } public JsonEntityBasic Parent { get; set; } diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityBasicForReference.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityBasicForReference.cs index c0228606e2e..d4b9e76854e 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityBasicForReference.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityBasicForReference.cs @@ -5,9 +5,21 @@ namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery; public class JsonEntityBasicForReference { - public int Id { get; set; } - public string Name { get; set; } + private int _id; + private string _name; + + public int Id + => _id; + + public string Name + => _name; public int? ParentId { get; set; } public JsonEntityBasic Parent { get; set; } + + public void SetIdAndName(int id, string name) + { + _id = id; + _name = name; + } } diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedBranch.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedBranch.cs index 999b6c8556c..ffa0eed4b50 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedBranch.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedBranch.cs @@ -9,9 +9,9 @@ public class JsonOwnedBranch public decimal Fraction { get; set; } public JsonEnum Enum { get; set; } - public JsonEnum? NullableEnum { get; set; } - public JsonEnum[] Enums { get; set; } - public JsonEnum?[] NullableEnums { get; set; } + public JsonEnum? NullableEnum { get; init; } + public JsonEnum[] Enums { get; init; } + public JsonEnum?[] NullableEnums { get; init; } public JsonOwnedLeaf OwnedReferenceLeaf { get; set; } public List OwnedCollectionLeaf { get; set; } diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs index 81796136632..373a835306e 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs @@ -329,7 +329,8 @@ public static IReadOnlyList CreateEntitiesBasic() public static IReadOnlyList CreateJsonEntitiesBasicForReference() { - var entity1 = new JsonEntityBasicForReference { Id = 1, Name = "EntityReference1" }; + var entity1 = new JsonEntityBasicForReference(); + entity1.SetIdAndName(1, "EntityReference1"); return new List { entity1 }; } diff --git a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs index c6b108148b0..ad8905611be 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs @@ -192,6 +192,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con x => x == true ? "Y" : "N")); }); + modelBuilder.Entity( + b => + { + b.Property(x => x.Id); + b.Property(x => x.Name); + }); + base.OnModelCreating(modelBuilder, context); } diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index a888a5810ae..d0fb6786d21 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -341,6 +341,30 @@ public virtual void Configuring_direction_on_RowsAffectedParameter_throws() public abstract class RelationalComplexTypeTestBase : ComplexTypeTestBase { + [ConditionalFact] + public virtual void Can_use_TPH() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Ignore() + .Entity(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .Ignore(c => c.Details) + .Ignore(c => c.Orders) + .Property(c => c.Name).IsRequired(); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties))!.GetComplexProperties().Single(); + var property = complexProperty.ComplexType.FindProperty(nameof(Customer.Name))!; + + Assert.True(property.IsColumnNullable()); + } + [ConditionalFact] public virtual void Can_use_table_splitting() { diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index 037a7e5f685..8aa6a51e038 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -894,9 +894,13 @@ protected interface IReplaceable int Property { get; set; } } - protected class ComplexProperties + protected class ComplexPropertiesBase { public int Id { get; set; } + } + + protected class ComplexProperties : ComplexPropertiesBase + { public required Customer Customer { get; set; } public required DoubleProperty DoubleProperty { get; set; } public required IndexedClass IndexedClass { get; set; }