diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index f2a6a01fe8d..a6c6ea9b389 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -16,6 +16,8 @@ namespace Microsoft.EntityFrameworkCore.Query; public partial class RelationalShapedQueryCompilingExpressionVisitor { + private ShaperProcessingExpressionVisitor? _currentShaperProcessor; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -24,6 +26,8 @@ public partial class RelationalShapedQueryCompilingExpressionVisitor /// public sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisitor { + private ShaperProcessingExpressionVisitor? _parentShaperProcessor; + /// /// Reading database values /// @@ -297,25 +301,35 @@ public LambdaExpression ProcessRelationalGroupingResult( out LambdaExpression? relatedDataLoaders, ref int collectionId) { - _inline = true; - keySelector = Lambda( - Visit(relationalGroupByResultExpression.KeyShaper), - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter); + _parentShaperProcessor = _parentVisitor._currentShaperProcessor; + _parentVisitor._currentShaperProcessor = this; - keyIdentifier = Lambda( - Visit(relationalGroupByResultExpression.KeyIdentifier), - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter); + try + { + _inline = true; + keySelector = Lambda( + Visit(relationalGroupByResultExpression.KeyShaper), + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter); - _inline = false; + keyIdentifier = Lambda( + Visit(relationalGroupByResultExpression.KeyIdentifier), + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter); + + _inline = false; - return ProcessShaper( - relationalGroupByResultExpression.ElementShaper, - out relationalCommandResolver!, - out readerColumns, - out relatedDataLoaders, - ref collectionId); + return ProcessShaper( + relationalGroupByResultExpression.ElementShaper, + out relationalCommandResolver!, + out readerColumns, + out relatedDataLoaders, + ref collectionId); + } + finally + { + _parentVisitor._currentShaperProcessor = _parentShaperProcessor; + } } /// @@ -331,145 +345,155 @@ public LambdaExpression ProcessShaper( out LambdaExpression? relatedDataLoaders, ref int collectionId) { - relatedDataLoaders = null; - _collectionId = collectionId; + _parentShaperProcessor = _parentVisitor._currentShaperProcessor; + _parentVisitor._currentShaperProcessor = this; - if (_indexMapParameter != null) + try { - var result = Visit(shaperExpression); - _expressions.Add(result); - result = Block(_variables, _expressions); - - relationalCommandResolver = _parentVisitor.CreateRelationalCommandResolverExpression(_selectExpression); - readerColumns = _readerColumns; + relatedDataLoaders = null; + _collectionId = collectionId; - return Lambda( - result, - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter, - _indexMapParameter); - } + if (_indexMapParameter != null) + { + var result = Visit(shaperExpression); + _expressions.Add(result); + result = Block(_variables, _expressions); - _containsCollectionMaterialization = new CollectionShaperFindingExpressionVisitor() - .ContainsCollectionMaterialization(shaperExpression); + relationalCommandResolver = _parentVisitor.CreateRelationalCommandResolverExpression(_selectExpression); + readerColumns = _readerColumns; - // for NoTrackingWithIdentityResolution we need to make sure we see JSON entities in the correct order - // specifically, if we project JSON collection, it needs to be projected before any individual element from that collection - // otherwise we store JSON entities in incorrect order in the Change Tracker, leading to possible data corruption - // we only need to do this once, on top level - // see issue #33073 for more context - if (_queryStateManager && !_isTracking && collectionId == 0) - { - var jsonCorrectOrderOfEntitiesForChangeTrackerValidator = - new JsonCorrectOrderOfEntitiesForChangeTrackerValidator(_selectExpression); - jsonCorrectOrderOfEntitiesForChangeTrackerValidator.Validate(shaperExpression); - } + return Lambda( + result, + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter, + _indexMapParameter); + } - if (!_containsCollectionMaterialization) - { - var result = Visit(shaperExpression); - _expressions.AddRange(_includeExpressions); - _expressions.AddRange(_jsonEntityExpressions); - _expressions.Add(result); - result = Block(_variables, _expressions); - - relationalCommandResolver = _generateCommandResolver - ? _parentVisitor.CreateRelationalCommandResolverExpression(_selectExpression) - : Constant(null, typeof(RelationalCommandResolver)); - readerColumns = _readerColumns; - - return Lambda( - result, - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter, - _resultContextParameter, - _resultCoordinatorParameter); - } - else - { - _valuesArrayExpression = MakeMemberAccess(_resultContextParameter, ResultContextValuesMemberInfo); - _collectionPopulatingExpressions = []; - _valuesArrayInitializers = []; + _containsCollectionMaterialization = new CollectionShaperFindingExpressionVisitor() + .ContainsCollectionMaterialization(shaperExpression); - var result = Visit(shaperExpression); + // for NoTrackingWithIdentityResolution we need to make sure we see JSON entities in the correct order + // specifically, if we project JSON collection, it needs to be projected before any individual element from that collection + // otherwise we store JSON entities in incorrect order in the Change Tracker, leading to possible data corruption + // we only need to do this once, on top level + // see issue #33073 for more context + if (_queryStateManager && !_isTracking && collectionId == 0) + { + var jsonCorrectOrderOfEntitiesForChangeTrackerValidator = + new JsonCorrectOrderOfEntitiesForChangeTrackerValidator(_selectExpression); + jsonCorrectOrderOfEntitiesForChangeTrackerValidator.Validate(shaperExpression); + } - var valueArrayInitializationExpression = Assign( - _valuesArrayExpression, NewArrayInit(typeof(object), _valuesArrayInitializers)); + if (!_containsCollectionMaterialization) + { + var result = Visit(shaperExpression); + _expressions.AddRange(_includeExpressions); + _expressions.AddRange(_jsonEntityExpressions); + _expressions.Add(result); + result = Block(_variables, _expressions); + + relationalCommandResolver = _generateCommandResolver + ? _parentVisitor.CreateRelationalCommandResolverExpression(_selectExpression) + : Constant(null, typeof(RelationalCommandResolver)); + readerColumns = _readerColumns; + + return Lambda( + result, + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter, + _resultContextParameter, + _resultCoordinatorParameter); + } + else + { + _valuesArrayExpression = MakeMemberAccess(_resultContextParameter, ResultContextValuesMemberInfo); + _collectionPopulatingExpressions = []; + _valuesArrayInitializers = []; - _expressions.AddRange(_jsonEntityExpressions); - _expressions.Add(valueArrayInitializationExpression); - _expressions.AddRange(_includeExpressions); + var result = Visit(shaperExpression); - if (_splitQuery) - { - _expressions.Add(Default(result.Type)); + var valueArrayInitializationExpression = Assign( + _valuesArrayExpression, NewArrayInit(typeof(object), _valuesArrayInitializers)); - var initializationBlock = Block(_variables, _expressions); - result = Condition( - Equal(_valuesArrayExpression, Constant(null, typeof(object[]))), - initializationBlock, - result); + _expressions.AddRange(_jsonEntityExpressions); + _expressions.Add(valueArrayInitializationExpression); + _expressions.AddRange(_includeExpressions); - if (_isAsync) + if (_splitQuery) { - var tasks = NewArrayInit( - typeof(Func), _collectionPopulatingExpressions.Select( - e => Lambda>(e))); - relatedDataLoaders = - Lambda>( - Call(TaskAwaiterMethodInfo, tasks), - QueryCompilationContext.QueryContextParameter, - _executionStrategyParameter!, - _resultCoordinatorParameter); + _expressions.Add(Default(result.Type)); + + var initializationBlock = Block(_variables, _expressions); + result = Condition( + Equal(_valuesArrayExpression, Constant(null, typeof(object[]))), + initializationBlock, + result); + + if (_isAsync) + { + var tasks = NewArrayInit( + typeof(Func), _collectionPopulatingExpressions.Select( + e => Lambda>(e))); + relatedDataLoaders = + Lambda>( + Call(TaskAwaiterMethodInfo, tasks), + QueryCompilationContext.QueryContextParameter, + _executionStrategyParameter!, + _resultCoordinatorParameter); + } + else + { + relatedDataLoaders = + Lambda>( + Block(_collectionPopulatingExpressions), + QueryCompilationContext.QueryContextParameter, + _executionStrategyParameter!, + _resultCoordinatorParameter); + } } else { - relatedDataLoaders = - Lambda>( - Block(_collectionPopulatingExpressions), - QueryCompilationContext.QueryContextParameter, - _executionStrategyParameter!, - _resultCoordinatorParameter); - } - } - else - { - var initializationBlock = Block(_variables, _expressions); + var initializationBlock = Block(_variables, _expressions); - var conditionalMaterializationExpressions = new List + var conditionalMaterializationExpressions = new List { IfThen( Equal(_valuesArrayExpression, Constant(null, typeof(object[]))), initializationBlock) }; - conditionalMaterializationExpressions.AddRange(_collectionPopulatingExpressions); + conditionalMaterializationExpressions.AddRange(_collectionPopulatingExpressions); - conditionalMaterializationExpressions.Add( - Condition( - IsTrue( - MakeMemberAccess( - _resultCoordinatorParameter, SingleQueryResultCoordinatorResultReadyMemberInfo)), - result, - Default(result.Type))); + conditionalMaterializationExpressions.Add( + Condition( + IsTrue( + MakeMemberAccess( + _resultCoordinatorParameter, SingleQueryResultCoordinatorResultReadyMemberInfo)), + result, + Default(result.Type))); - result = Block(conditionalMaterializationExpressions); - } + result = Block(conditionalMaterializationExpressions); + } - relationalCommandResolver = _generateCommandResolver - ? _parentVisitor.CreateRelationalCommandResolverExpression(_selectExpression) - : Constant(null, typeof(RelationalCommandCache)); - ; - readerColumns = _readerColumns; + relationalCommandResolver = _generateCommandResolver + ? _parentVisitor.CreateRelationalCommandResolverExpression(_selectExpression) + : Constant(null, typeof(RelationalCommandCache)); + ; + readerColumns = _readerColumns; - collectionId = _collectionId; + collectionId = _collectionId; - return Lambda( - result, - QueryCompilationContext.QueryContextParameter, - _dataReaderParameter, - _resultContextParameter, - _resultCoordinatorParameter); + return Lambda( + result, + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter, + _resultContextParameter, + _resultCoordinatorParameter); + } + } + finally + { + _parentVisitor._currentShaperProcessor = _parentShaperProcessor; } } @@ -575,9 +599,9 @@ protected override Expression VisitExtension(Expression extensionExpression) switch (extensionExpression) { case RelationalStructuralTypeShaperExpression - { - ValueBufferExpression: ProjectionBindingExpression projectionBindingExpression - } shaper + { + ValueBufferExpression: ProjectionBindingExpression projectionBindingExpression + } shaper when !_inline: { // we can't cache ProjectionBindingExpression results for non-tracking queries @@ -696,30 +720,6 @@ protected override Expression VisitExtension(Expression extensionExpression) entityParameter, shaper.Type); - if (GetProjectionIndex(projectionBindingExpression) is IDictionary propertyMap) - { - foreach (var (property, projectionIndex) in propertyMap) - { - if (property is IComplexProperty { ComplexType: var complexType } complexProperty - && complexType.IsMappedToJson()) - { - var jsonReaderDataVariable = GenerateJsonReader(projectionIndex, complexType); - - var shaperResult = CreateJsonShapers( - complexType, - nullable: true, // TODO - jsonReaderDataVariable, - keyValuesParameter: null!, // TODO - containerEntityExpression: entityParameter, - relationship: complexProperty); - - var visitedShaperResult = Visit(shaperResult); - - _includeExpressions.Add(visitedShaperResult); - } - } - } - break; } } @@ -2473,7 +2473,42 @@ private bool IsPropertyAssignment( } } - private ParameterExpression GenerateJsonReader(int jsonColumnIndex, ITypeBase structuralType) + /// + /// Injects JSON shaper code for JSON-mapped complex properties. + /// + internal void ProcessTopLevelComplexJsonProperties( + StructuralTypeShaperExpression shaper, + ParameterExpression instanceVariable, + List expressions) + { + // Note that the following processes only top-level complex properties (where the projection is a dictionary of properties to their projection index). + // For nested JSON types, CreateJsonShapers calls itself recursively. + if (shaper is RelationalStructuralTypeShaperExpression { ValueBufferExpression: ProjectionBindingExpression projectionBindingExpression } + && GetProjectionIndex(projectionBindingExpression) is Dictionary propertyMap) + { + foreach (var (property, projectionIndex) in propertyMap) + { + if (property is IComplexProperty { ComplexType: var complexType } complexProperty + && complexType.IsMappedToJson()) + { + var jsonReaderDataVariable = GenerateJsonReader(projectionIndex, complexType); + + var shaperResult = CreateJsonShapers( + complexType, + nullable: shaper.IsNullable || complexProperty.IsNullable, + jsonReaderDataVariable, + keyValuesParameter: null, // For owned entities only + containerEntityExpression: instanceVariable, + relationship: complexProperty); + + var visitedShaperResult = Visit(shaperResult); + expressions.Add(visitedShaperResult); + } + } + } + } + + internal ParameterExpression GenerateJsonReader(int jsonColumnIndex, ITypeBase structuralType) { Check.DebugAssert(structuralType.IsMappedToJson()); diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index dbbe1c92358..1c97795b66a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -484,6 +484,27 @@ private static Expression CreateReaderColumnsExpression( return result; } + /// + /// Called after a structural type is materialized, but before it's handed off to the change tracker. + /// Here we inject the JSON shapers for any complex JSON properties the type has. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 void AddStructuralTypeInitialization( + StructuralTypeShaperExpression shaper, + ParameterExpression instanceVariable, + List variables, + List expressions) + { + Check.DebugAssert(_currentShaperProcessor is not null); + + _currentShaperProcessor.ProcessTopLevelComplexJsonProperties(shaper, instanceVariable, expressions); + } + private Expression CreateRelationalCommandResolverExpression(Expression queryExpression) { // In the regular case, we generate code that accesses the RelationalCommandCache (which invokes the 2nd part of the diff --git a/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs b/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs index 3fec2e421c5..2745b19b62d 100644 --- a/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs +++ b/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs @@ -140,7 +140,7 @@ IServiceProperty serviceProperty => serviceProperty.ParameterBinding.BindToParameter(bindingInfo), IComplexProperty { IsCollection: true } complexProperty - => Expression.Default(complexProperty.ClrType), // Initialize collections to null, they'll be populated separately + => Default(complexProperty.ClrType), // Initialize collections to null, they'll be populated separately IComplexProperty complexProperty => CreateMaterializeExpression( diff --git a/src/EFCore/Query/LiftableConstantExpressionHelpers.cs b/src/EFCore/Query/LiftableConstantExpressionHelpers.cs index a372036b624..8c5a8d81522 100644 --- a/src/EFCore/Query/LiftableConstantExpressionHelpers.cs +++ b/src/EFCore/Query/LiftableConstantExpressionHelpers.cs @@ -150,7 +150,7 @@ public static Expression BuildMemberAccessForEntityOrComplexType( /// 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 static Expression> BuildMemberAccessLambdaForEntityOrComplexType( + public static Expression> BuildMemberAccessLambdaForStructuralType( ITypeBase type) { var prm = Parameter(typeof(MaterializerLiftableConstantContext)); diff --git a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs index bec097af0b9..bf13c3e75c8 100644 --- a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs @@ -55,6 +55,7 @@ protected ShapedQueryCompilingExpressionVisitor( _structuralTypeMaterializerInjector = new StructuralTypeMaterializerInjector( + this, dependencies.EntityMaterializerSource, dependencies.LiftableConstantFactory, queryCompilationContext.QueryTrackingBehavior, @@ -233,12 +234,12 @@ protected override Expression VisitConstant(ConstantExpression constantExpressio { { Value: IEntityType entityTypeValue } => liftableConstantFactory.CreateLiftableConstant( constantExpression.Value, - LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForEntityOrComplexType(entityTypeValue), + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForStructuralType(entityTypeValue), entityTypeValue.ShortName() + "EntityType", constantExpression.Type), { Value: IComplexType complexTypeValue } => liftableConstantFactory.CreateLiftableConstant( constantExpression.Value, - LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForEntityOrComplexType(complexTypeValue), + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForStructuralType(complexTypeValue), complexTypeValue.ShortName() + "ComplexType", constantExpression.Type), { Value: IProperty propertyValue } => liftableConstantFactory.CreateLiftableConstant( @@ -361,8 +362,20 @@ protected override Expression VisitExtension(Expression extensionExpression) } } + /// + /// Called after a structural type is materialized, but before it's handed off to the change tracker. + /// + public virtual void AddStructuralTypeInitialization( + StructuralTypeShaperExpression shaper, + ParameterExpression instanceVariable, + List variables, + List expressions) + { + } + private sealed class StructuralTypeMaterializerInjector( - IStructuralTypeMaterializerSource entityMaterializerSource, + ShapedQueryCompilingExpressionVisitor shapedQueryCompiler, + IStructuralTypeMaterializerSource materializerSource, ILiftableConstantFactory liftableConstantFactory, QueryTrackingBehavior queryTrackingBehavior, bool supportsPrecompiledQuery) @@ -426,10 +439,10 @@ bool ContainsOwner(IEntityType? owner) protected override Expression VisitExtension(Expression extensionExpression) => extensionExpression is StructuralTypeShaperExpression shaper - ? ProcessEntityShaper(shaper) + ? ProcessStructuralTypeShaper(shaper) : base.VisitExtension(extensionExpression); - private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) + private Expression ProcessStructuralTypeShaper(StructuralTypeShaperExpression shaper) { _currentEntityIndex++; @@ -565,7 +578,7 @@ private Expression ProcessEntityShaper(StructuralTypeShaperExpression shaper) supportsPrecompiledQuery ? liftableConstantFactory.CreateLiftableConstant( typeBase, - LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForEntityOrComplexType(typeBase), + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForStructuralType(typeBase), typeBase.Name + "EntityType", typeof(IEntityType)) : Constant(typeBase), @@ -606,7 +619,7 @@ private Expression MaterializeEntity( ParameterExpression instanceVariable, ParameterExpression? entryVariable) { - var typeBase = shaper.StructuralType; + var structuralType = shaper.StructuralType; var expressions = new List(); var variables = new List(); @@ -626,7 +639,7 @@ private Expression MaterializeEntity( typeof(ISnapshot)) : Constant(Snapshot.Empty, typeof(ISnapshot)))); - var returnType = typeBase.ClrType; + var returnType = structuralType.ClrType; var valueBufferExpression = Call(materializationContextVariable, MaterializationContext.GetValueBufferMethod); var materializationConditionBody = ReplacingExpressionVisitor.Replace( @@ -637,23 +650,23 @@ private Expression MaterializeEntity( var expressionContext = (returnType, materializationContextVariable, concreteEntityTypeVariable, shadowValuesVariable); expressions.Add(Assign(concreteEntityTypeVariable, materializationConditionBody)); - var (primaryKey, concreteEntityTypes) = typeBase is IEntityType entityType + var (primaryKey, concreteStructuralTypes) = structuralType is IEntityType entityType ? (entityType.FindPrimaryKey(), entityType.GetConcreteDerivedTypesInclusive().Cast().ToArray()) - : (null, [typeBase]); + : (null, [structuralType]); - var switchCases = new SwitchCase[concreteEntityTypes.Length]; - for (var i = 0; i < concreteEntityTypes.Length; i++) + var switchCases = new SwitchCase[concreteStructuralTypes.Length]; + for (var i = 0; i < concreteStructuralTypes.Length; i++) { - var concreteEntityType = concreteEntityTypes[i]; + var concreteStructuralType = concreteStructuralTypes[i]; switchCases[i] = SwitchCase( - CreateFullMaterializeExpression(concreteEntityTypes[i], expressionContext), + CreateFullMaterializeExpression(concreteStructuralTypes[i], expressionContext), supportsPrecompiledQuery ? liftableConstantFactory.CreateLiftableConstant( - concreteEntityTypes[i], - LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForEntityOrComplexType(concreteEntityType), - concreteEntityType.ShortName() + (typeBase is IEntityType ? "EntityType" : "ComplexType"), - typeBase is IEntityType ? typeof(IEntityType) : typeof(IComplexType)) - : Constant(concreteEntityTypes[i], typeBase is IEntityType ? typeof(IEntityType) : typeof(IComplexType))); + concreteStructuralTypes[i], + LiftableConstantExpressionHelpers.BuildMemberAccessLambdaForStructuralType(concreteStructuralType), + concreteStructuralType.ShortName() + (structuralType is IEntityType ? "EntityType" : "ComplexType"), + structuralType is IEntityType ? typeof(IEntityType) : typeof(IComplexType)) + : Constant(concreteStructuralTypes[i], structuralType is IEntityType ? typeof(IEntityType) : typeof(IComplexType))); } var materializationExpression = Switch( @@ -663,9 +676,11 @@ private Expression MaterializeEntity( expressions.Add(Assign(instanceVariable, materializationExpression)); + shapedQueryCompiler.AddStructuralTypeInitialization(shaper, instanceVariable, variables, expressions); + if (_queryStateManager && primaryKey is not null) { - if (typeBase is IEntityType entityType2) + if (structuralType is IEntityType entityType2) { foreach (var et in entityType2.GetAllBaseTypes().Concat(entityType2.GetDerivedTypesInclusive())) { @@ -696,7 +711,7 @@ private Expression MaterializeEntity( } private BlockExpression CreateFullMaterializeExpression( - ITypeBase concreteTypeBase, + ITypeBase concreteStructuralType, (Type ReturnType, ParameterExpression MaterializationContextVariable, ParameterExpression ConcreteEntityTypeVariable, @@ -709,14 +724,14 @@ private BlockExpression CreateFullMaterializeExpression( var blockExpressions = new List(2); - var materializer = entityMaterializerSource + var materializer = materializerSource .CreateMaterializeExpression( new StructuralTypeMaterializerSourceParameters( - concreteTypeBase, "instance", queryTrackingBehavior), materializationContextVariable); + concreteStructuralType, "instance", queryTrackingBehavior), materializationContextVariable); // TODO: Properly support shadow properties for complex types #35613 if (_queryStateManager - && concreteTypeBase is IRuntimeEntityType { ShadowPropertyCount: > 0 } runtimeEntityType) + && concreteStructuralType is IRuntimeEntityType { ShadowPropertyCount: > 0 } runtimeEntityType) { var valueBufferExpression = Call( materializationContextVariable, MaterializationContext.GetValueBufferMethod); diff --git a/test/EFCore.Relational.Specification.Tests/Update/ComplexCollectionJsonUpdateTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/ComplexCollectionJsonUpdateTestBase.cs index 247a5fb50c0..d951a771d9e 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/ComplexCollectionJsonUpdateTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/ComplexCollectionJsonUpdateTestBase.cs @@ -11,7 +11,7 @@ public abstract class ComplexCollectionJsonUpdateTestBase(TFixture fix protected ComplexCollectionJsonContext CreateContext() => (ComplexCollectionJsonContext)Fixture.CreateContext(); - [ConditionalFact(Skip = "Issue #36433")] + [ConditionalFact] public virtual Task Add_element_to_complex_collection_mapped_to_json() => TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, @@ -26,17 +26,10 @@ public virtual Task Add_element_to_complex_collection_mapped_to_json() company.Contacts!.Add(new Contact { Name = "New Contact", PhoneNumbers = ["555-0000"] }); - Assert.Equal(""" -CompanyWithComplexCollections {Id: 1} Unchanged - Id: 1 PK - Name: 'Test Company' - Contacts (Complex: List) - Department (Complex: Department) - Budget: 10000.00 - Name: 'Initial Department' - Employees (Complex: List) - -""", context.ChangeTracker.DebugView.LongView); + Assert.Contains("Contacts (Complex: List)", context.ChangeTracker.DebugView.LongView); + Assert.Contains("Department (Complex: Department)", context.ChangeTracker.DebugView.LongView); + Assert.Contains("Name: 'Initial Department'", context.ChangeTracker.DebugView.LongView); + Assert.Contains("Employees (Complex: List)", context.ChangeTracker.DebugView.LongView); ClearLog(); await context.SaveChangesAsync(); @@ -126,7 +119,7 @@ public virtual Task Move_elements_in_complex_collection_mapped_to_json() } }); - [ConditionalFact(Skip = "Issue #36433")] + [ConditionalFact] public virtual Task Change_complex_collection_mapped_to_json_to_null_and_to_empty() => TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, @@ -354,7 +347,7 @@ public virtual Task Modify_nested_complex_property_in_complex_collection_mapped_ } }); - [ConditionalFact(Skip = "Issue #36433")] + [ConditionalFact] public virtual Task Set_complex_collection_to_null_mapped_to_json() => TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, @@ -499,7 +492,7 @@ public virtual Task Complex_collection_with_empty_nested_collections_mapped_to_j } }); - [ConditionalFact(Skip = "Issue #36433")] + [ConditionalFact] public virtual Task Set_complex_property_mapped_to_json_to_null() => TestHelpers.ExecuteWithStrategyInTransactionAsync( CreateContext, diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/ComplexCollectionJsonUpdateSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/ComplexCollectionJsonUpdateSqlServerTest.cs index 8fc1b5fb1a8..0ad7a4e3f29 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/ComplexCollectionJsonUpdateSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/ComplexCollectionJsonUpdateSqlServerTest.cs @@ -33,15 +33,13 @@ public override async Task Remove_element_from_complex_collection_mapped_to_json AssertSql( """ @p0='[{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 66) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Contacts] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -52,15 +50,13 @@ public override async Task Modify_element_in_complex_collection_mapped_to_json() AssertSql( """ @p0='[{"Name":"First Contact - Modified","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 141) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Contacts] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -71,15 +67,13 @@ public override async Task Move_elements_in_complex_collection_mapped_to_json() AssertSql( """ @p0='[{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]},{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Contacts] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -117,16 +111,14 @@ public override async Task Complex_collection_with_nested_complex_type_mapped_to AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"John Doe","PhoneNumbers":["555-1234","555-5678"],"Address":{"City":"Seattle","Country":"USA","PostalCode":"98101","Street":"123 Main St"}},{"Name":"Jane Smith","PhoneNumbers":["555-9876"],"Address":{"City":"Portland","Country":"USA","PostalCode":"97201","Street":"456 Oak Ave"}}]' (Nullable = false) (Size = 289) -@p3='1' +@p0='[{"Name":"John Doe","PhoneNumbers":["555-1234","555-5678"],"Address":{"City":"Seattle","Country":"USA","PostalCode":"98101","Street":"123 Main St"}},{"Name":"Jane Smith","PhoneNumbers":["555-9876"],"Address":{"City":"Portland","Country":"USA","PostalCode":"97201","Street":"456 Oak Ave"}}]' (Nullable = false) (Size = 289) +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Employees] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -138,14 +130,13 @@ public override async Task Modify_multiple_complex_properties_mapped_to_json() """ @p0='[{"Name":"Contact 1","PhoneNumbers":["555-1111"]}]' (Nullable = false) (Size = 50) @p1='{"Budget":50000.00,"Name":"Department A"}' (Nullable = false) (Size = 41) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p2='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p2; """); } @@ -156,15 +147,13 @@ public override async Task Clear_complex_collection_mapped_to_json() AssertSql( """ @p0='[]' (Nullable = false) (Size = 2) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Contacts] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -175,15 +164,13 @@ public override async Task Replace_entire_complex_collection_mapped_to_json() AssertSql( """ @p0='[{"Name":"Replacement Contact 1","PhoneNumbers":["999-1111"]},{"Name":"Replacement Contact 2","PhoneNumbers":["999-2222","999-3333"]}]' (Nullable = false) (Size = 134) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Contacts] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -193,16 +180,14 @@ public override async Task Add_element_to_nested_complex_collection_mapped_to_js AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001","555-9999"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 163) -@p3='1' +@p0='[{"Name":"Initial Employee","PhoneNumbers":["555-0001","555-9999"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 163) +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Employees] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -212,16 +197,14 @@ public override async Task Modify_nested_complex_property_in_complex_collection_ AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Modified City","Country":"USA","PostalCode":"99999","Street":"100 First St"}}]' (Nullable = false) (Size = 153) -@p3='1' +@p0='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Modified City","Country":"USA","PostalCode":"99999","Street":"100 First St"}}]' (Nullable = false) (Size = 153) +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Employees] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -265,16 +248,14 @@ public override async Task Replace_complex_collection_element_mapped_to_json() AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Replacement Employee","PhoneNumbers":["555-7777","555-8888"],"Address":{"City":"Replace City","Country":"Canada","PostalCode":"54321","Street":"789 Replace St"}}]' (Nullable = false) (Size = 172) -@p3='1' +@p0='[{"Name":"Replacement Employee","PhoneNumbers":["555-7777","555-8888"],"Address":{"City":"Replace City","Country":"Canada","PostalCode":"54321","Street":"789 Replace St"}}]' (Nullable = false) (Size = 172) +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Employees] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -284,16 +265,14 @@ public override async Task Complex_collection_with_empty_nested_collections_mapp AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":10000.00,"Name":"Initial Department"}' (Nullable = false) (Size = 47) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}},{"Name":"Employee No Phone","PhoneNumbers":[],"Address":{"City":"Quiet City","Country":"USA","PostalCode":"00000","Street":"456 No Phone St"}}]' (Nullable = false) (Size = 295) -@p3='1' +@p0='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}},{"Name":"Employee No Phone","PhoneNumbers":[],"Address":{"City":"Quiet City","Country":"USA","PostalCode":"00000","Street":"456 No Phone St"}}]' (Nullable = false) (Size = 295) +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Employees] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } @@ -337,16 +316,14 @@ public override async Task Replace_complex_property_mapped_to_json() AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":99999.99,"Name":"Replacement Department"}' (Nullable = false) (Size = 51) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p0='{"Budget":99999.99,"Name":"Replacement Department"}' (Nullable = false) (Size = 51) +@p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [Companies] SET [Contacts] = @p0, [Department] = @p1, [Employees] = @p2 +UPDATE [Companies] SET [Department] = @p0 OUTPUT 1 -WHERE [Id] = @p3; +WHERE [Id] = @p1; """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Update/ComplexCollectionJsonUpdateSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Update/ComplexCollectionJsonUpdateSqliteTest.cs index db11e377884..0b4491b38c0 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Update/ComplexCollectionJsonUpdateSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Update/ComplexCollectionJsonUpdateSqliteTest.cs @@ -31,12 +31,10 @@ public override async Task Remove_element_from_complex_collection_mapped_to_json AssertSql( """ @p0='[{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 66) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Contacts" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -48,12 +46,10 @@ public override async Task Modify_element_in_complex_collection_mapped_to_json() AssertSql( """ @p0='[{"Name":"First Contact - Modified","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 141) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Contacts" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -65,12 +61,10 @@ public override async Task Move_elements_in_complex_collection_mapped_to_json() AssertSql( """ @p0='[{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]},{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Contacts" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -105,13 +99,11 @@ public override async Task Complex_collection_with_nested_complex_type_mapped_to AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"John Doe","PhoneNumbers":["555-1234","555-5678"],"Address":{"City":"Seattle","Country":"USA","PostalCode":"98101","Street":"123 Main St"}},{"Name":"Jane Smith","PhoneNumbers":["555-9876"],"Address":{"City":"Portland","Country":"USA","PostalCode":"97201","Street":"456 Oak Ave"}}]' (Nullable = false) (Size = 289) -@p3='1' +@p0='[{"Name":"John Doe","PhoneNumbers":["555-1234","555-5678"],"Address":{"City":"Seattle","Country":"USA","PostalCode":"98101","Street":"123 Main St"}},{"Name":"Jane Smith","PhoneNumbers":["555-9876"],"Address":{"City":"Portland","Country":"USA","PostalCode":"97201","Street":"456 Oak Ave"}}]' (Nullable = false) (Size = 289) +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Employees" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -124,11 +116,10 @@ public override async Task Modify_multiple_complex_properties_mapped_to_json() """ @p0='[{"Name":"Contact 1","PhoneNumbers":["555-1111"]}]' (Nullable = false) (Size = 50) @p1='{"Budget":"50000.0","Name":"Department A"}' (Nullable = false) (Size = 42) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p2='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1 +WHERE "Id" = @p2 RETURNING 1; """); } @@ -140,12 +131,10 @@ public override async Task Clear_complex_collection_mapped_to_json() AssertSql( """ @p0='[]' (Nullable = false) (Size = 2) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Contacts" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -157,12 +146,10 @@ public override async Task Replace_entire_complex_collection_mapped_to_json() AssertSql( """ @p0='[{"Name":"Replacement Contact 1","PhoneNumbers":["999-1111"]},{"Name":"Replacement Contact 2","PhoneNumbers":["999-2222","999-3333"]}]' (Nullable = false) (Size = 134) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Contacts" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -173,13 +160,11 @@ public override async Task Add_element_to_nested_complex_collection_mapped_to_js AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001","555-9999"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 163) -@p3='1' +@p0='[{"Name":"Initial Employee","PhoneNumbers":["555-0001","555-9999"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 163) +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Employees" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -190,13 +175,11 @@ public override async Task Modify_nested_complex_property_in_complex_collection_ AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Modified City","Country":"USA","PostalCode":"99999","Street":"100 First St"}}]' (Nullable = false) (Size = 153) -@p3='1' +@p0='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Modified City","Country":"USA","PostalCode":"99999","Street":"100 First St"}}]' (Nullable = false) (Size = 153) +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Employees" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -237,13 +220,11 @@ public override async Task Replace_complex_collection_element_mapped_to_json() AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Replacement Employee","PhoneNumbers":["555-7777","555-8888"],"Address":{"City":"Replace City","Country":"Canada","PostalCode":"54321","Street":"789 Replace St"}}]' (Nullable = false) (Size = 172) -@p3='1' +@p0='[{"Name":"Replacement Employee","PhoneNumbers":["555-7777","555-8888"],"Address":{"City":"Replace City","Country":"Canada","PostalCode":"54321","Street":"789 Replace St"}}]' (Nullable = false) (Size = 172) +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Employees" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -254,13 +235,11 @@ public override async Task Complex_collection_with_empty_nested_collections_mapp AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":"10000.0","Name":"Initial Department"}' (Nullable = false) (Size = 48) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}},{"Name":"Employee No Phone","PhoneNumbers":[],"Address":{"City":"Quiet City","Country":"USA","PostalCode":"00000","Street":"456 No Phone St"}}]' (Nullable = false) (Size = 295) -@p3='1' +@p0='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}},{"Name":"Employee No Phone","PhoneNumbers":[],"Address":{"City":"Quiet City","Country":"USA","PostalCode":"00000","Street":"456 No Phone St"}}]' (Nullable = false) (Size = 295) +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Employees" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); } @@ -301,13 +280,11 @@ public override async Task Replace_complex_property_mapped_to_json() AssertSql( """ -@p0='[{"Name":"First Contact","PhoneNumbers":["555-1234","555-5678"]},{"Name":"Second Contact","PhoneNumbers":["555-9876","555-5432"]}]' (Nullable = false) (Size = 130) -@p1='{"Budget":"99999.99","Name":"Replacement Department"}' (Nullable = false) (Size = 53) -@p2='[{"Name":"Initial Employee","PhoneNumbers":["555-0001"],"Address":{"City":"Initial City","Country":"USA","PostalCode":"00001","Street":"100 First St"}}]' (Nullable = false) (Size = 152) -@p3='1' +@p0='{"Budget":"99999.99","Name":"Replacement Department"}' (Nullable = false) (Size = 53) +@p1='1' -UPDATE "Companies" SET "Contacts" = @p0, "Department" = @p1, "Employees" = @p2 -WHERE "Id" = @p3 +UPDATE "Companies" SET "Department" = @p0 +WHERE "Id" = @p1 RETURNING 1; """); }