Skip to content

Commit

Permalink
Fix to #32911 - ComplexProperty with AsSplitQuery
Browse files Browse the repository at this point in the history
Fixes #32911
  • Loading branch information
maumar committed Feb 5, 2024
1 parent 5e9e4eb commit e81b57e
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 35 deletions.
75 changes: 40 additions & 35 deletions src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1296,8 +1296,7 @@ void ProcessType(StructuralTypeProjectionExpression typeProjection)

foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(typeProjection.StructuralType))
{
ProcessType(
(StructuralTypeProjectionExpression)typeProjection.BindComplexProperty(complexProperty).ValueBufferExpression);
ProcessType((StructuralTypeProjectionExpression)typeProjection.BindComplexProperty(complexProperty).ValueBufferExpression);
}
}

Expand Down Expand Up @@ -3525,16 +3524,18 @@ private SqlRemappingVisitor PushdownIntoSubqueryInternal(bool liftOrderings = tr

return sqlRemappingVisitor;


StructuralTypeProjectionExpression LiftEntityProjectionFromSubquery(
StructuralTypeProjectionExpression projection,
string subqueryAlias)
{
var propertyExpressions = new Dictionary<IProperty, ColumnExpression>();

HandleTypeProjection(projection);
return BuildProjectionExpression(projection);

void HandleTypeProjection(StructuralTypeProjectionExpression typeProjection)
StructuralTypeProjectionExpression BuildProjectionExpression(StructuralTypeProjectionExpression typeProjection)
{
var propertyExpressions = new Dictionary<IProperty, ColumnExpression>();
var complexPropertyCache = new Dictionary<IComplexProperty, StructuralTypeShaperExpression>();

foreach (var property in typeProjection.StructuralType.GetAllPropertiesInHierarchy())
{
// json entity projection (i.e. JSON entity that was transformed into query root) may have synthesized keys
Expand All @@ -3548,52 +3549,56 @@ void HandleTypeProjection(StructuralTypeProjectionExpression typeProjection)

var innerColumn = typeProjection.BindProperty(property);
var outerColumn = subquery.GenerateOuterColumn(subqueryAlias, innerColumn);

projectionMap[innerColumn] = outerColumn;
propertyExpressions[property] = outerColumn;
}


foreach (var complexProperty in GetAllComplexPropertiesInHierarchy(typeProjection.StructuralType))
{
HandleTypeProjection(
(StructuralTypeProjectionExpression)typeProjection.BindComplexProperty(complexProperty).ValueBufferExpression);
var complexPropertyShaper = typeProjection.BindComplexProperty(complexProperty);
var complexTypeProjectionExpression = BuildProjectionExpression((StructuralTypeProjectionExpression)complexPropertyShaper.ValueBufferExpression);
complexPropertyCache[complexProperty] = complexPropertyShaper.Update(complexTypeProjectionExpression);
}
}

ColumnExpression? discriminatorExpression = null;
if (projection.DiscriminatorExpression != null)
{
discriminatorExpression = subquery.GenerateOuterColumn(
subqueryAlias, projection.DiscriminatorExpression, DiscriminatorColumnAlias);
projectionMap[projection.DiscriminatorExpression] = discriminatorExpression;
}
ColumnExpression? discriminatorExpression = null;
if (typeProjection.DiscriminatorExpression != null)
{
discriminatorExpression = subquery.GenerateOuterColumn(
subqueryAlias, typeProjection.DiscriminatorExpression, DiscriminatorColumnAlias);
projectionMap[typeProjection.DiscriminatorExpression] = discriminatorExpression;
}

var tableMap = projection.TableMap.ToDictionary(kvp => kvp.Key, _ => subqueryAlias);
var tableMap = projection.TableMap.ToDictionary(kvp => kvp.Key, _ => subqueryAlias);

var newEntityProjection = new StructuralTypeProjectionExpression(
projection.StructuralType, propertyExpressions, tableMap, nullable: false, discriminatorExpression);
var newEntityProjection = new StructuralTypeProjectionExpression(
typeProjection.StructuralType, propertyExpressions, complexPropertyCache, tableMap, nullable: false, discriminatorExpression);

if (projection.StructuralType is IEntityType entityType2)
{
// Also lift nested entity projections
foreach (var navigation in entityType2
.GetAllBaseTypes().Concat(entityType2.GetDerivedTypesInclusive())
.SelectMany(t => t.GetDeclaredNavigations()))

if (typeProjection.StructuralType is IEntityType entityType2)
{
var boundEntityShaperExpression = projection.BindNavigation(navigation);
if (boundEntityShaperExpression != null)
// Also lift nested entity projections
foreach (var navigation in entityType2
.GetAllBaseTypes().Concat(entityType2.GetDerivedTypesInclusive())
.SelectMany(t => t.GetDeclaredNavigations()))
{
var newValueBufferExpression =
boundEntityShaperExpression.ValueBufferExpression is StructuralTypeProjectionExpression innerEntityProjection
? (Expression)LiftEntityProjectionFromSubquery(innerEntityProjection, subqueryAlias)
: LiftJsonQueryFromSubquery((JsonQueryExpression)boundEntityShaperExpression.ValueBufferExpression);
var boundEntityShaperExpression = projection.BindNavigation(navigation);
if (boundEntityShaperExpression != null)
{
var newValueBufferExpression =
boundEntityShaperExpression.ValueBufferExpression is StructuralTypeProjectionExpression innerEntityProjection
? (Expression)LiftEntityProjectionFromSubquery(innerEntityProjection, subqueryAlias)
: LiftJsonQueryFromSubquery((JsonQueryExpression)boundEntityShaperExpression.ValueBufferExpression);

boundEntityShaperExpression = boundEntityShaperExpression.Update(newValueBufferExpression);
newEntityProjection.AddNavigationBinding(navigation, boundEntityShaperExpression);
boundEntityShaperExpression = boundEntityShaperExpression.Update(newValueBufferExpression);
newEntityProjection.AddNavigationBinding(navigation, boundEntityShaperExpression);
}
}
}
}

return newEntityProjection;
return newEntityProjection;
}
}

JsonQueryExpression LiftJsonQueryFromSubquery(JsonQueryExpression jsonQueryExpression)
Expand Down
43 changes: 43 additions & 0 deletions src/EFCore.Relational/Query/StructuralTypeProjectionExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,49 @@ public StructuralTypeProjectionExpression(
{
}

/// <summary>
/// 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.
/// </summary>
[EntityFrameworkInternal]
public StructuralTypeProjectionExpression(
ITypeBase type,
IReadOnlyDictionary<IProperty, ColumnExpression> propertyExpressionMap,
Dictionary<IComplexProperty, StructuralTypeShaperExpression> complexPropertyCache,
IReadOnlyDictionary<ITableBase, string> tableMap,
bool nullable = false,
SqlExpression? discriminatorExpression = null)
: this(
type,
propertyExpressionMap,
new Dictionary<INavigation, StructuralTypeShaperExpression>(),
complexPropertyCache,
tableMap,
nullable,
discriminatorExpression)
{
}

private StructuralTypeProjectionExpression(
ITypeBase type,
IReadOnlyDictionary<IProperty, ColumnExpression> propertyExpressionMap,
Dictionary<INavigation, StructuralTypeShaperExpression> ownedNavigationMap,
Dictionary<IComplexProperty, StructuralTypeShaperExpression> complexPropertyCache,
IReadOnlyDictionary<ITableBase, string> tableMap,
bool nullable,
SqlExpression? discriminatorExpression = null)
{
StructuralType = type;
_propertyExpressionMap = propertyExpressionMap;
_ownedNavigationMap = ownedNavigationMap;
_complexPropertyCache = complexPropertyCache;
TableMap = tableMap;
IsNullable = nullable;
DiscriminatorExpression = discriminatorExpression;
}

private StructuralTypeProjectionExpression(
ITypeBase type,
IReadOnlyDictionary<IProperty, ColumnExpression> propertyExpressionMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2319,4 +2319,103 @@ ELSE NULL
END = N'COUNTRY'
""");
}



#nullable enable

[ConditionalFact]
public void Kupson()
{
using (var ctx = new MyContext())
{
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
ctx.Add(new Offer
{
Variations = [
new Variation
{
Payment = new Payment(120, 100),
Nested = new NestedEntity
{
Payment = new Payment(12, 10),
},
}
]
});
ctx.SaveChanges();
ctx.ChangeTracker.Clear(); // If I don't clear the change tracker, it also works.

var entity = ctx.Offers
.Include(e => e.Variations!)
.ThenInclude(v => v.Nested)
.AsSplitQuery() // If I comment this out, it works



.ToList();
//.Single(e => e.Id == 1);

//var one = entity.Variations?.Single().Payment;
//var two = entity.Variations?.Single().Nested?.Payment;

}
}

public class MyContext : DbContext
{
public DbSet<Offer> Offers => Set<Offer>();


protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Repro;Trusted_Connection=True;MultipleActiveResultSets=true");
}

protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Variation>().ComplexProperty(p => p.Payment, price =>
{
price.Property(p => p.Netto).HasColumnName("payment_netto"); // If I change the column name to a different than the others, it also works
price.Property(p => p.Brutto).HasColumnName("payment_brutto");
});
builder.Entity<NestedEntity>().ComplexProperty(p => p.Payment, price =>
{
price.Property(p => p.Netto).HasColumnName("payment_netto");
price.Property(p => p.Brutto).HasColumnName("payment_brutto");
});
}
}

public abstract class EntityBase
{
[System.ComponentModel.DataAnnotations.Key]
public int Id { get; set; }
}

public class Offer : EntityBase
{
public ICollection<Variation>? Variations { get; set; }
}

public class Variation : EntityBase
{
public Payment Payment { get; set; } = new Payment(0, 0);

public NestedEntity? Nested { get; set; }
}

public class NestedEntity : EntityBase
{
public Payment Payment { get; set; } = new Payment(0, 0);
}


public record Payment(decimal Netto, decimal Brutto);

#nullable disable



}
Loading

0 comments on commit e81b57e

Please sign in to comment.