Skip to content

Commit c6a0602

Browse files
committed
Fix to #7499 - Where() on derived property fails with undefined property on base class exception
Problem was that we were not taking type differences and expression converts in various places during compilation. We sometimes remove converts when processing expressions, but then not accounting for them when we re-assemble the visited expression. This caused many scenarios involving casts (e.g. accessing properties/navigations on derived types) to fail. Fix is to start compensating for those where necessary. Created helper methods for EF.Property method call, member access and assignment and using them instead of regular Expression APIs. Also fixed unrelated issue in the SqlTranslatingExpressionVisitor - when trying to translate MethodCall expression, we would force client evaluation, if arguments to the function was a subquery - this is not ideal, since we can translate some of the subqueries (ones that return single result).
1 parent 2747992 commit c6a0602

31 files changed

+1083
-155
lines changed

src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Linq.Expressions;
77
using JetBrains.Annotations;
8+
using Microsoft.EntityFrameworkCore.Extensions.Internal;
89
using Microsoft.EntityFrameworkCore.Internal;
910
using Microsoft.EntityFrameworkCore.Metadata.Internal;
1011
using Microsoft.EntityFrameworkCore.Query.Expressions;
@@ -106,7 +107,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
106107
return methodCallExpression;
107108
}
108109

109-
if (EntityQueryModelVisitor.IsPropertyMethod(methodCallExpression.Method))
110+
if (methodCallExpression.Method.IsEFPropertyMethod())
110111
{
111112
var newArg0 = Visit(methodCallExpression.Arguments[0]);
112113

src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Linq.Expressions;
88
using JetBrains.Annotations;
9+
using Microsoft.EntityFrameworkCore.Extensions.Internal;
910
using Microsoft.EntityFrameworkCore.Internal;
1011
using Microsoft.EntityFrameworkCore.Metadata;
1112
using Microsoft.EntityFrameworkCore.Query.Expressions;
@@ -375,7 +376,7 @@ private void AnalyzeTestExpression(Expression expression)
375376
}
376377

377378
if (expression is MethodCallExpression methodCallExpression
378-
&& EntityQueryModelVisitor.IsPropertyMethod(methodCallExpression.Method))
379+
&& methodCallExpression.Method.IsEFPropertyMethod())
379380
{
380381
if (methodCallExpression.Arguments[0] is QuerySourceReferenceExpression querySourceCaller)
381382
{
@@ -421,7 +422,7 @@ protected override Expression VisitMember(MemberExpression node)
421422

422423
protected override Expression VisitMethodCall(MethodCallExpression node)
423424
{
424-
if (EntityQueryModelVisitor.IsPropertyMethod(node.Method))
425+
if (node.Method.IsEFPropertyMethod())
425426
{
426427
if (node.Arguments[1] is ConstantExpression propertyNameExpression && (string)propertyNameExpression.Value == _propertyName)
427428
{
@@ -579,9 +580,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
579580
{
580581
var arguments
581582
= methodCallExpression.Arguments
582-
.Where(e => !(e is QuerySourceReferenceExpression)
583-
&& !(e is SubQueryExpression))
584-
.Select(e => (e as ConstantExpression)?.Value is Array || e.Type == typeof(DbFunctions)
583+
.Where(e => !(e.RemoveConvert() is QuerySourceReferenceExpression)
584+
&& !IsNonTranslatableSubquery(e.RemoveConvert()))
585+
.Select(e => (e.RemoveConvert() as ConstantExpression)?.Value is Array || e.RemoveConvert().Type == typeof(DbFunctions)
585586
? e
586587
: Visit(e))
587588
.Where(e => e != null)
@@ -625,6 +626,12 @@ var projectionIndex
625626
?? _queryModelVisitor.BindMethodToOuterQueryParameter(methodCallExpression);
626627
}
627628

629+
private bool IsNonTranslatableSubquery(Expression expression)
630+
=> expression is SubQueryExpression subQueryExpression
631+
&& !(subQueryExpression.QueryModel.GetOutputDataInfo() is StreamedScalarValueInfo
632+
|| subQueryExpression.QueryModel.GetOutputDataInfo() is StreamedSingleValueInfo streamedSingleValueInfo
633+
&& IsStreamedSingleValueSupportedType(streamedSingleValueInfo));
634+
628635
/// <summary>
629636
/// Visit a member expression.
630637
/// </summary>
@@ -636,8 +643,8 @@ protected override Expression VisitMember(MemberExpression memberExpression)
636643
{
637644
Check.NotNull(memberExpression, nameof(memberExpression));
638645

639-
if (!(memberExpression.Expression is QuerySourceReferenceExpression)
640-
&& !(memberExpression.Expression is SubQueryExpression))
646+
if (!(memberExpression.Expression.RemoveConvert() is QuerySourceReferenceExpression)
647+
&& !(memberExpression.Expression.RemoveConvert() is SubQueryExpression))
641648
{
642649
var newExpression = Visit(memberExpression.Expression);
643650

@@ -842,18 +849,9 @@ protected override Expression VisitSubQuery(SubQueryExpression expression)
842849
}
843850
else if (!(subQueryOutputDataInfo is StreamedSequenceInfo))
844851
{
845-
var streamedSingleValueInfo = subQueryOutputDataInfo as StreamedSingleValueInfo;
846-
847-
var streamedSingleValueSupportedType
848-
= streamedSingleValueInfo != null
849-
&& _relationalTypeMapper.FindMapping(
850-
streamedSingleValueInfo.DataType
851-
.UnwrapNullableType()
852-
.UnwrapEnumType()) != null;
853-
854852
if (_inProjection
855853
&& !(subQueryOutputDataInfo is StreamedScalarValueInfo)
856-
&& !streamedSingleValueSupportedType)
854+
&& !IsStreamedSingleValueSupportedType(subQueryOutputDataInfo))
857855
{
858856
return null;
859857
}
@@ -901,6 +899,13 @@ var queriesProjectionCountMapping
901899
return null;
902900
}
903901

902+
private bool IsStreamedSingleValueSupportedType(IStreamedDataInfo outputDataInfo)
903+
=> outputDataInfo is StreamedSingleValueInfo streamedSingleValueInfo
904+
&& _relationalTypeMapper.FindMapping(
905+
streamedSingleValueInfo.DataType
906+
.UnwrapNullableType()
907+
.UnwrapEnumType()) != null;
908+
904909
/// <summary>
905910
/// Visits a constant expression.
906911
/// </summary>
@@ -1023,8 +1028,8 @@ var entityType
10231028
if (entityType != null)
10241029
{
10251030
return Visit(
1026-
EntityQueryModelVisitor.CreatePropertyExpression(
1027-
expression, entityType.FindPrimaryKey().Properties[0]));
1031+
expression.CreateEFPropertyExpression(
1032+
entityType.FindPrimaryKey().Properties[0]));
10281033
}
10291034

10301035
return null;

src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,8 @@ var concreteEntityTypes
10481048
var discriminatorProperty
10491049
= _relationalAnnotationProvider.For(concreteEntityTypes[0]).DiscriminatorProperty;
10501050

1051-
var discriminatorPropertyExpression = CreatePropertyExpression(typeBinaryExpression.Expression, discriminatorProperty);
1051+
var discriminatorPropertyExpression
1052+
= typeBinaryExpression.Expression.CreateEFPropertyExpression(discriminatorProperty);
10521053

10531054
var discriminatorPredicate
10541055
= concreteEntityTypes
@@ -1839,8 +1840,8 @@ var parameterWithSamePrefixCount
18391840

18401841
var querySourceReference = new QuerySourceReferenceExpression(querySource);
18411842
var propertyExpression = isMemberExpression
1842-
? Expression.Property(querySourceReference, property.PropertyInfo)
1843-
: CreatePropertyExpression(querySourceReference, property);
1843+
? querySourceReference.CreateEFPropertyExpression(property.PropertyInfo)
1844+
: querySourceReference.CreateEFPropertyExpression(property);
18441845

18451846
if (propertyExpression.Type.GetTypeInfo().IsValueType)
18461847
{

src/EFCore.Specification.Tests/GearsOfWarQueryFixtureBase.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ protected virtual void OnModelCreating(ModelBuilder modelBuilder)
4646
b.HasOne(sm => sm.Mission).WithMany(m => m.ParticipatingSquads).HasForeignKey(sm => sm.MissionId);
4747
b.HasOne(sm => sm.Squad).WithMany(s => s.Missions).HasForeignKey(sm => sm.SquadId);
4848
});
49+
50+
modelBuilder.Entity<Faction>().HasKey(f => f.Id);
51+
modelBuilder.Entity<LocustHorde>().HasBaseType<Faction>();
52+
modelBuilder.Entity<LocustHorde>().HasMany(h => h.Leaders).WithOne();
53+
modelBuilder.Entity<LocustHorde>().HasOne(h => h.Commander).WithOne(c => c.CommandingFaction);
54+
55+
modelBuilder.Entity<LocustLeader>().HasKey(l => l.Name);
56+
modelBuilder.Entity<LocustCommander>().HasBaseType<LocustLeader>();
57+
modelBuilder.Entity<LocustCommander>().HasOne(c => c.DefeatedBy).WithOne().HasForeignKey<LocustCommander>(c => new { c.DefeatedByNickname, c.DefeatedBySquadId });
4958
}
5059
}
5160
}

0 commit comments

Comments
 (0)