Skip to content

Commit

Permalink
Query: Bind with members when type has cast to interface
Browse files Browse the repository at this point in the history
Resolves #17276
Resolves #17099
Resolves #16759
  • Loading branch information
smitpatel committed Aug 27, 2019
1 parent e12d885 commit 1057757
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ private bool TryBindMember(Expression source, MemberIdentity member, out Express

if (source is EntityProjectionExpression entityProjectionExpression)
{
if (convertedType.IsInterface
&& convertedType.IsAssignableFrom(entityProjectionExpression.Type))
{
convertedType = entityProjectionExpression.Type;
}

expression = member.MemberInfo != null
? entityProjectionExpression.BindMember(member.MemberInfo, convertedType, clientEval: false, out _)
: entityProjectionExpression.BindMember(member.Name, convertedType, clientEval: false, out _);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ private Expression BindProperty(Expression source, string propertyName, Type typ
if (source is EntityProjectionExpression entityProjection)
{
var entityType = entityProjection.EntityType;
if (convertedType != null)
if (convertedType != null
&& !(convertedType.IsInterface
&& convertedType.IsAssignableFrom(entityType.ClrType)))
{
entityType = entityType.GetRootType().GetDerivedTypesInclusive()
.FirstOrDefault(et => et.ClrType == convertedType);

if (entityType == null)
{
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,12 @@ private bool TryBindMember(Expression source, MemberIdentity member, out Express
if (source is EntityProjectionExpression entityProjectionExpression)
{
var entityType = entityProjectionExpression.EntityType;
if (convertedType != null)
if (convertedType != null
&& !(convertedType.IsInterface
&& convertedType.IsAssignableFrom(entityType.ClrType)))
{
entityType = entityType.GetRootType().GetDerivedTypesInclusive()
.FirstOrDefault(et => et.ClrType == convertedType);

if (entityType == null)
{
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,12 @@ private Expression TryExpandNavigation(Expression root, MemberIdentity memberIde
if (UnwrapEntityReference(innerExpression) is EntityReference entityReference)
{
var entityType = entityReference.EntityType;
if (convertedType != null)
if (convertedType != null
&& !(convertedType.IsInterface
&& convertedType.IsAssignableFrom(entityType.ClrType)))
{
entityType = entityType.GetTypesInHierarchy().FirstOrDefault(et => et.ClrType == convertedType);
entityType = entityType.GetTypesInHierarchy()
.FirstOrDefault(et => et.ClrType == convertedType);
if (entityType == null)
{
return null;
Expand Down
142 changes: 130 additions & 12 deletions test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3523,12 +3523,7 @@ public void Include_with_order_by_on_interface_key()
{
using (var context = new MyContext10635(_options))
{
Assert.Equal(
CoreStrings.TranslationFailed("(p) => ((IEntity10635)p).Id"),
Assert.Throws<InvalidOperationException>(
() => context.Parents.Include(p => p.Children).OrderBy(p => ((IEntity10635)p).Id).ToList()).Message);

var query2 = context.Parents.Include(p => p.Children).OrderBy(p => EF.Property<int>(p, "Id")).ToList();
var query = context.Parents.Include(p => p.Children).OrderBy(p => ((IEntity10635)p).Id).ToList();

AssertSql(
@"SELECT [p].[Id], [p].[Name], [c].[Id], [c].[Name], [c].[Parent10635Id], [c].[ParentId]
Expand All @@ -3546,12 +3541,7 @@ public void Correlated_collection_with_order_by_on_interface_key()
{
using (var context = new MyContext10635(_options))
{
Assert.Equal(
CoreStrings.TranslationFailed("(p) => ((IEntity10635)p).Id"),
Assert.Throws<InvalidOperationException>(
() => context.Parents.OrderBy(p => ((IEntity10635)p).Id).Select(p => p.Children.ToList()).ToList()).Message);

var query2 = context.Parents.OrderBy(p => EF.Property<int>(p, "Id")).Select(p => p.Children.ToList()).ToList();
var query = context.Parents.OrderBy(p => ((IEntity10635)p).Id).Select(p => p.Children.ToList()).ToList();

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

#endregion

#region Bug17276_17099_16759

[ConditionalFact]
public virtual void Expression_tree_constructed_via_interface_works_17276()
{
using (CreateDatabase17276())
{
using (var context = new MyContext17276(_options))
{
var query = List(context.RemovableEntities);

AssertSql(
@"SELECT [r].[Id], [r].[IsRemoved], [r].[Removed], [r].[RemovedByUser]
FROM [RemovableEntities] AS [r]
WHERE [r].[IsRemoved] <> CAST(1 AS bit)");
}
}
}

[ConditionalFact]
public virtual void Expression_tree_constructed_via_interface_for_navigation_works_17099()
{
using (CreateDatabase17276())
{
using (var context = new MyContext17276(_options))
{
var query = context.Parents
.Where(p => EF.Property<bool>(EF.Property<IRemovable17276>(p, "RemovableEntity"), "IsRemoved"))
.ToList();

AssertSql(
@"SELECT [p].[Id], [p].[RemovableEntityId]
FROM [Parents] AS [p]
LEFT JOIN [RemovableEntities] AS [r] ON [p].[RemovableEntityId] = [r].[Id]
WHERE [r].[IsRemoved] = CAST(1 AS bit)");
}
}
}

[ConditionalFact]
public virtual void Expression_tree_constructed_via_interface_works_16759()
{
using (CreateDatabase17276())
{
using (var context = new MyContext17276(_options))
{
var specification = new Specification<Parent17276>(1);
var entities = context.Set<Parent17276>().Where(specification.Criteria).ToList();

AssertSql(
@"@__id_0='1'
SELECT [p].[Id], [p].[RemovableEntityId]
FROM [Parents] AS [p]
WHERE ([p].[Id] = @__id_0) AND @__id_0 IS NOT NULL");
}
}
}

public class MyContext17276 : DbContext
{
public DbSet<RemovableEntity17276> RemovableEntities { get; set; }
public DbSet<Parent17276> Parents { get; set; }
public MyContext17276(DbContextOptions options) : base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}

private SqlServerTestStore CreateDatabase17276()
=> CreateTestStore(
() => new MyContext17276(_options),
context =>
{
context.SaveChanges();
ClearLog();
});

public static List<T> List<T>(IQueryable<T> query) where T : IRemovable17276
{
return query.Where(x => !x.IsRemoved).ToList();
}

public interface IRemovable17276
{
bool IsRemoved { get; set; }

string RemovedByUser { get; set; }

DateTime? Removed { get; set; }
}

public class RemovableEntity17276 : IRemovable17276
{
public int Id { get; set; }
public bool IsRemoved { get; set; }
public string RemovedByUser { get; set; }
public DateTime? Removed { get; set; }
}

public class Parent17276 : IHasId<int>
{
public int Id { get; set; }
public RemovableEntity17276 RemovableEntity { get; set; }
}

public interface IHasId<out T>
{
T Id { get; }
}

public class Specification<T>
where T : IHasId<int>
{
public Expression<Func<T, bool>> Criteria { get; }

public Specification(int id)
{
Criteria = t => t.Id == id;
}
}

#endregion

private DbContextOptions _options;

private SqlServerTestStore CreateTestStore<TContext>(
Expand Down

0 comments on commit 1057757

Please sign in to comment.