Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query: Bind with members when type has cast to interface #17438

Merged
merged 1 commit into from
Aug 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ private bool TryBindMember(Expression source, MemberIdentity member, out Express

if (source is EntityProjectionExpression entityProjectionExpression)
{
if (convertedType != null
&& 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 = List17276(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 Specification17276<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> List17276<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 : IHasId17276<int>
{
public int Id { get; set; }
public RemovableEntity17276 RemovableEntity { get; set; }
}

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

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

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

#endregion

private DbContextOptions _options;

private SqlServerTestStore CreateTestStore<TContext>(
Expand Down