Skip to content

Commit 010b45d

Browse files
committed
Query: Bind with members when type has cast to interface
Resolves #17276 Resolves #17099 Resolves #16759
1 parent 8e18281 commit 010b45d

File tree

5 files changed

+148
-18
lines changed

5 files changed

+148
-18
lines changed

src/EFCore.Cosmos/Query/Internal/CosmosSqlTranslatingExpressionVisitor.cs

+7
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ private bool TryBindMember(Expression source, MemberIdentity member, out Express
123123

124124
if (source is EntityProjectionExpression entityProjectionExpression)
125125
{
126+
if (convertedType != null
127+
&& convertedType.IsInterface
128+
&& convertedType.IsAssignableFrom(entityProjectionExpression.Type))
129+
{
130+
convertedType = entityProjectionExpression.Type;
131+
}
132+
126133
expression = member.MemberInfo != null
127134
? entityProjectionExpression.BindMember(member.MemberInfo, convertedType, clientEval: false, out _)
128135
: entityProjectionExpression.BindMember(member.Name, convertedType, clientEval: false, out _);

src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,12 @@ private Expression BindProperty(Expression source, string propertyName, Type typ
9797
if (source is EntityProjectionExpression entityProjection)
9898
{
9999
var entityType = entityProjection.EntityType;
100-
if (convertedType != null)
100+
if (convertedType != null
101+
&& !(convertedType.IsInterface
102+
&& convertedType.IsAssignableFrom(entityType.ClrType)))
101103
{
102104
entityType = entityType.GetRootType().GetDerivedTypesInclusive()
103105
.FirstOrDefault(et => et.ClrType == convertedType);
104-
105106
if (entityType == null)
106107
{
107108
return null;

src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,12 @@ private bool TryBindMember(Expression source, MemberIdentity member, out Express
207207
if (source is EntityProjectionExpression entityProjectionExpression)
208208
{
209209
var entityType = entityProjectionExpression.EntityType;
210-
if (convertedType != null)
210+
if (convertedType != null
211+
&& !(convertedType.IsInterface
212+
&& convertedType.IsAssignableFrom(entityType.ClrType)))
211213
{
212214
entityType = entityType.GetRootType().GetDerivedTypesInclusive()
213215
.FirstOrDefault(et => et.ClrType == convertedType);
214-
215216
if (entityType == null)
216217
{
217218
return false;

src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,12 @@ private Expression TryExpandNavigation(Expression root, MemberIdentity memberIde
103103
if (UnwrapEntityReference(innerExpression) is EntityReference entityReference)
104104
{
105105
var entityType = entityReference.EntityType;
106-
if (convertedType != null)
106+
if (convertedType != null
107+
&& !(convertedType.IsInterface
108+
&& convertedType.IsAssignableFrom(entityType.ClrType)))
107109
{
108-
entityType = entityType.GetTypesInHierarchy().FirstOrDefault(et => et.ClrType == convertedType);
110+
entityType = entityType.GetTypesInHierarchy()
111+
.FirstOrDefault(et => et.ClrType == convertedType);
109112
if (entityType == null)
110113
{
111114
return null;

test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs

+130-12
Original file line numberDiff line numberDiff line change
@@ -3523,12 +3523,7 @@ public void Include_with_order_by_on_interface_key()
35233523
{
35243524
using (var context = new MyContext10635(_options))
35253525
{
3526-
Assert.Equal(
3527-
CoreStrings.TranslationFailed("(p) => ((IEntity10635)p).Id"),
3528-
Assert.Throws<InvalidOperationException>(
3529-
() => context.Parents.Include(p => p.Children).OrderBy(p => ((IEntity10635)p).Id).ToList()).Message);
3530-
3531-
var query2 = context.Parents.Include(p => p.Children).OrderBy(p => EF.Property<int>(p, "Id")).ToList();
3526+
var query = context.Parents.Include(p => p.Children).OrderBy(p => ((IEntity10635)p).Id).ToList();
35323527

35333528
AssertSql(
35343529
@"SELECT [p].[Id], [p].[Name], [c].[Id], [c].[Name], [c].[Parent10635Id], [c].[ParentId]
@@ -3546,12 +3541,7 @@ public void Correlated_collection_with_order_by_on_interface_key()
35463541
{
35473542
using (var context = new MyContext10635(_options))
35483543
{
3549-
Assert.Equal(
3550-
CoreStrings.TranslationFailed("(p) => ((IEntity10635)p).Id"),
3551-
Assert.Throws<InvalidOperationException>(
3552-
() => context.Parents.OrderBy(p => ((IEntity10635)p).Id).Select(p => p.Children.ToList()).ToList()).Message);
3553-
3554-
var query2 = context.Parents.OrderBy(p => EF.Property<int>(p, "Id")).Select(p => p.Children.ToList()).ToList();
3544+
var query = context.Parents.OrderBy(p => ((IEntity10635)p).Id).Select(p => p.Children.ToList()).ToList();
35553545

35563546
AssertSql(
35573547
@"SELECT [p].[Id], [c].[Id], [c].[Name], [c].[Parent10635Id], [c].[ParentId]
@@ -6330,6 +6320,134 @@ public class EntityWithQueryFilterCycle3
63306320

63316321
#endregion
63326322

6323+
#region Bug17276_17099_16759
6324+
6325+
[ConditionalFact]
6326+
public virtual void Expression_tree_constructed_via_interface_works_17276()
6327+
{
6328+
using (CreateDatabase17276())
6329+
{
6330+
using (var context = new MyContext17276(_options))
6331+
{
6332+
var query = List17276(context.RemovableEntities);
6333+
6334+
AssertSql(
6335+
@"SELECT [r].[Id], [r].[IsRemoved], [r].[Removed], [r].[RemovedByUser]
6336+
FROM [RemovableEntities] AS [r]
6337+
WHERE [r].[IsRemoved] <> CAST(1 AS bit)");
6338+
}
6339+
}
6340+
}
6341+
6342+
[ConditionalFact]
6343+
public virtual void Expression_tree_constructed_via_interface_for_navigation_works_17099()
6344+
{
6345+
using (CreateDatabase17276())
6346+
{
6347+
using (var context = new MyContext17276(_options))
6348+
{
6349+
var query = context.Parents
6350+
.Where(p => EF.Property<bool>(EF.Property<IRemovable17276>(p, "RemovableEntity"), "IsRemoved"))
6351+
.ToList();
6352+
6353+
AssertSql(
6354+
@"SELECT [p].[Id], [p].[RemovableEntityId]
6355+
FROM [Parents] AS [p]
6356+
LEFT JOIN [RemovableEntities] AS [r] ON [p].[RemovableEntityId] = [r].[Id]
6357+
WHERE [r].[IsRemoved] = CAST(1 AS bit)");
6358+
}
6359+
}
6360+
}
6361+
6362+
[ConditionalFact]
6363+
public virtual void Expression_tree_constructed_via_interface_works_16759()
6364+
{
6365+
using (CreateDatabase17276())
6366+
{
6367+
using (var context = new MyContext17276(_options))
6368+
{
6369+
var specification = new Specification17276<Parent17276>(1);
6370+
var entities = context.Set<Parent17276>().Where(specification.Criteria).ToList();
6371+
6372+
AssertSql(
6373+
@"@__id_0='1'
6374+
6375+
SELECT [p].[Id], [p].[RemovableEntityId]
6376+
FROM [Parents] AS [p]
6377+
WHERE ([p].[Id] = @__id_0) AND @__id_0 IS NOT NULL");
6378+
}
6379+
}
6380+
}
6381+
6382+
public class MyContext17276 : DbContext
6383+
{
6384+
public DbSet<RemovableEntity17276> RemovableEntities { get; set; }
6385+
public DbSet<Parent17276> Parents { get; set; }
6386+
public MyContext17276(DbContextOptions options) : base(options)
6387+
{
6388+
}
6389+
6390+
protected override void OnModelCreating(ModelBuilder modelBuilder)
6391+
{
6392+
}
6393+
}
6394+
6395+
private SqlServerTestStore CreateDatabase17276()
6396+
=> CreateTestStore(
6397+
() => new MyContext17276(_options),
6398+
context =>
6399+
{
6400+
context.SaveChanges();
6401+
6402+
ClearLog();
6403+
});
6404+
6405+
public static List<T> List17276<T>(IQueryable<T> query) where T : IRemovable17276
6406+
{
6407+
return query.Where(x => !x.IsRemoved).ToList();
6408+
}
6409+
6410+
public interface IRemovable17276
6411+
{
6412+
bool IsRemoved { get; set; }
6413+
6414+
string RemovedByUser { get; set; }
6415+
6416+
DateTime? Removed { get; set; }
6417+
}
6418+
6419+
public class RemovableEntity17276 : IRemovable17276
6420+
{
6421+
public int Id { get; set; }
6422+
public bool IsRemoved { get; set; }
6423+
public string RemovedByUser { get; set; }
6424+
public DateTime? Removed { get; set; }
6425+
}
6426+
6427+
public class Parent17276 : IHasId17276<int>
6428+
{
6429+
public int Id { get; set; }
6430+
public RemovableEntity17276 RemovableEntity { get; set; }
6431+
}
6432+
6433+
public interface IHasId17276<out T>
6434+
{
6435+
T Id { get; }
6436+
}
6437+
6438+
public class Specification17276<T>
6439+
where T : IHasId17276<int>
6440+
{
6441+
public Expression<Func<T, bool>> Criteria { get; }
6442+
6443+
public Specification17276(int id)
6444+
{
6445+
Criteria = t => t.Id == id;
6446+
}
6447+
}
6448+
6449+
#endregion
6450+
63336451
private DbContextOptions _options;
63346452

63356453
private SqlServerTestStore CreateTestStore<TContext>(

0 commit comments

Comments
 (0)