$compute inside $expand not translating $this for single navigations
Xriuk opened this issue · comments
Assemblies affected
ASP.NET Core OData 8.2.4
Describe the bug
$this
is working correctly inside a nested $compute
in $expand
when expanding a collection navigation, but not when expanding a single navigation.
Reproduce steps
The following request url which expands a collection navigation:
https://.../odata/DisposalContainers?$expand=Packagings($select=Id,Test;$compute=$this/Material/Code as Test)
Produces an expression like this, which works:
DbSet<DisposalContainer>()
.Select($it => new SelectAllAndExpand<DisposalContainer>{
Model = TypedLinqParameterContainer<IEdmModel>.TypedProperty,
Instance = $it,
UseInstanceForProperties = True,
Container = new NamedProperty<IEnumerable<SelectSome<DisposalPackaging>>>{
Name = "Packagings",
Value = $it.Packagings
.Select($it => new SelectSome<DisposalPackaging>{
Model = TypedLinqParameterContainer<IEdmModel>.TypedProperty,
Container = new NamedPropertyWithNext0<int?>{
Name = "Id",
Value = (int?)$it.Id,
Next0 = new NamedProperty<string>{
Name = "Test",
Value = $it.Material.Code
}
}
}
)
}
}
)
While a request url like this which expands a single navigation:
https://.../odata/DisposalMaterials?$expand=Destination($select=Id,Test;$compute=$this/Color/Value as Test)
Produces an expression like this, which throws the exception below:
DbSet<DisposalMaterial>()
.Select($it => new SelectAllAndExpand<DisposalMaterial>{
Model = TypedLinqParameterContainer<IEdmModel>.TypedProperty,
Instance = $it,
UseInstanceForProperties = True,
Container = new SingleExpandedProperty<SelectSome<DisposalDestination>>{
Name = "Destination",
Value = new SelectSome<DisposalDestination>{
Model = TypedLinqParameterContainer<IEdmModel>.TypedProperty,
Container = new NamedPropertyWithNext0<int?>{
Name = "Id",
Value = (int?)$it.Destination.Id,
Next0 = new NamedProperty<string>{
Name = "Test",
Value = $it.Color.Value
}
}
}
,
IsNull = (int?)$it.Destination.Id == null
}
}
)
Specifically in this part
Next0 = new NamedProperty<string>{
Name = "Test",
Value = $it.Color.Value
}
It creates an $it
expression (like above for the collection), which appears to be different from the top $it
(DbSet<DisposalMaterial>().Select($it => ...)
), because otherwise the error would have been different (like not being able to cast DisposalMaterial to DisposalDestination, or DisposalMaterial not having a Color property). It throws an exception like this:
The LINQ expression '$it' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Data Model
public class DisposalContainer {
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; } = null!;
public ICollection<DisposalPackaging>? Packagings { get; set; }
}
public class DisposalPackaging {
[Key]
public int Id { get; set; }
[Required]
public DisposalMaterial? Material { get; set; }
public ICollection<DisposalContainer>? Containers { get; set; }
}
public class DisposalMaterial {
[Key]
public int Id { get; set; }
[Required]
[MaxLength(10)]
public string Code { get; set; } = null!;
[Required]
public DisposalDestination? Destination { get; set; }
public ICollection<DisposalPackaging>? Packagings { get; set; }
}
public class DisposalDestination {
[Key]
public int Id { get; set; }
[Required]
public Color Color { get; set; } = null!;
public ICollection<DisposalMaterial>? Materials { get; set; }
}
public class Color {
public string Value { get; set; } = null!;
}
Hi there, it appears that, for both of the above examples, $this
is not actually necessary (though there's nothing wrong with having it).
Regarding the error that you are seeing, from the OData standard:
The $this literal can be used in $filter and $orderby expressions nested within $expand and $select for collection-valued properties and navigation properties.
$this
is only allowed when the $compute
is nested in a $expand
on a collection-valued navigation property. This is why the first request succeeds and the second one fails, because Destination
is a single-valued navigation property.
My suggestion is to either remove the use of $this
entirely in these cases, or to only use $this
for collection-valued properties per the standard.