Skip to content

Commit

Permalink
Query: Allow entity equality to work on client converted entity (#21780)
Browse files Browse the repository at this point in the history
Resolves #21768
  • Loading branch information
smitpatel committed Jul 25, 2020
1 parent f203e1f commit 8be0230
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -478,22 +478,26 @@ MethodInfo GetMethod()
return null;
}

if (subqueryTranslation.ShaperExpression is EntityShaperExpression entityShaperExpression)
var shaperExpression = subqueryTranslation.ShaperExpression;
var innerExpression = shaperExpression;
Type convertedType = null;
if (shaperExpression is UnaryExpression unaryExpression
&& unaryExpression.NodeType == ExpressionType.Convert)
{
return new EntityReferenceExpression(subqueryTranslation);
convertedType = unaryExpression.Type;
innerExpression = unaryExpression.Operand;
}

var shaperExpression = subqueryTranslation.ShaperExpression;
if (shaperExpression is UnaryExpression unaryExpression
&& unaryExpression.NodeType == ExpressionType.Convert
&& unaryExpression.Type.MakeNullable() == unaryExpression.Operand.Type)
if (innerExpression is EntityShaperExpression entityShaperExpression
&& (convertedType == null
|| convertedType.IsAssignableFrom(entityShaperExpression.Type)))
{
shaperExpression = unaryExpression.Operand;
return new EntityReferenceExpression(subqueryTranslation.UpdateShaperExpression(innerExpression));
}

#pragma warning disable IDE0046 // Convert to conditional expression
if (!(shaperExpression is ProjectionBindingExpression projectionBindingExpression))
#pragma warning restore IDE0046 // Convert to conditional expression
if (!(innerExpression is ProjectionBindingExpression projectionBindingExpression
&& (convertedType == null
|| convertedType.MakeNullable() == innerExpression.Type)))
{
return null;
}
Expand Down Expand Up @@ -917,9 +921,23 @@ private Expression BindProperty(EntityReferenceExpression entityReferenceExpress
if (entityReferenceExpression.SubqueryEntity != null)
{
var entityShaper = (EntityShaperExpression)entityReferenceExpression.SubqueryEntity.ShaperExpression;
var readValueExpression = ((EntityProjectionExpression)Visit(entityShaper.ValueBufferExpression)).BindProperty(property);
var inMemoryQueryExpression = (InMemoryQueryExpression)entityReferenceExpression.SubqueryEntity.QueryExpression;

Expression readValueExpression;
var projectionBindingExpression = (ProjectionBindingExpression)entityShaper.ValueBufferExpression;
if (projectionBindingExpression.ProjectionMember != null)
{
var entityProjectionExpression = (EntityProjectionExpression)inMemoryQueryExpression.GetMappedProjection(
projectionBindingExpression.ProjectionMember);
readValueExpression = entityProjectionExpression.BindProperty(property);
}
else
{
// This has to be index map since entities cannot map to just integer index
var index = projectionBindingExpression.IndexMap[property];
readValueExpression = inMemoryQueryExpression.Projection[index];
}

return ProcessSingleResultScalar(
inMemoryQueryExpression.ServerQueryExpression,
readValueExpression,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,16 +549,26 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method)
return null;
}

if (subqueryTranslation.ShaperExpression is EntityShaperExpression entityShaperExpression)
var shaperExpression = subqueryTranslation.ShaperExpression;
var innerExpression = shaperExpression;
Type convertedType = null;
if (shaperExpression is UnaryExpression unaryExpression
&& unaryExpression.NodeType == ExpressionType.Convert)
{
return new EntityReferenceExpression(subqueryTranslation);
convertedType = unaryExpression.Type;
innerExpression = unaryExpression.Operand;
}

if (!(subqueryTranslation.ShaperExpression is ProjectionBindingExpression
|| (subqueryTranslation.ShaperExpression is UnaryExpression unaryExpression
&& unaryExpression.NodeType == ExpressionType.Convert
&& unaryExpression.Type.MakeNullable() == unaryExpression.Operand.Type
&& unaryExpression.Operand is ProjectionBindingExpression)
if (innerExpression is EntityShaperExpression entityShaperExpression
&& (convertedType == null
|| convertedType.IsAssignableFrom(entityShaperExpression.Type)))
{
return new EntityReferenceExpression(subqueryTranslation.UpdateShaperExpression(innerExpression));
}

if (!((innerExpression is ProjectionBindingExpression
&& (convertedType == null
|| convertedType.MakeNullable() == innerExpression.Type))
|| IsAggregateResultWithCustomShaper(methodCallExpression.Method)))
{
return null;
Expand All @@ -583,11 +593,11 @@ static bool IsAggregateResultWithCustomShaper(MethodInfo method)
SqlExpression scalarSubqueryExpression = new ScalarSubqueryExpression(subquery);

if (subqueryTranslation.ResultCardinality == ResultCardinality.SingleOrDefault
&& !subqueryTranslation.ShaperExpression.Type.IsNullableType())
&& !shaperExpression.Type.IsNullableType())
{
scalarSubqueryExpression = _sqlExpressionFactory.Coalesce(
scalarSubqueryExpression,
(SqlExpression)Visit(subqueryTranslation.ShaperExpression.Type.GetDefaultValueConstant()));
(SqlExpression)Visit(shaperExpression.Type.GetDefaultValueConstant()));
}

return scalarSubqueryExpression;
Expand Down Expand Up @@ -942,8 +952,24 @@ private SqlExpression BindProperty(EntityReferenceExpression entityReferenceExpr
if (entityReferenceExpression.SubqueryEntity != null)
{
var entityShaper = (EntityShaperExpression)entityReferenceExpression.SubqueryEntity.ShaperExpression;
var innerProjection = ((EntityProjectionExpression)Visit(entityShaper.ValueBufferExpression)).BindProperty(property);
var subSelectExpression = (SelectExpression)entityReferenceExpression.SubqueryEntity.QueryExpression;

SqlExpression innerProjection;
var projectionBindingExpression = (ProjectionBindingExpression)entityShaper.ValueBufferExpression;
if (projectionBindingExpression.ProjectionMember != null)
{
var entityProjectionExpression = (EntityProjectionExpression)subSelectExpression.GetMappedProjection(
projectionBindingExpression.ProjectionMember);
innerProjection = entityProjectionExpression.BindProperty(property);
}
else
{
// This has to be index map since entities cannot map to just integer index
var index = projectionBindingExpression.IndexMap[property];
innerProjection = subSelectExpression.Projection[index].Expression;
subSelectExpression.ClearProjection();
}

subSelectExpression.AddToProjection(innerProjection);

return new ScalarSubqueryExpression(subSelectExpression);
Expand Down
113 changes: 113 additions & 0 deletions test/EFCore.InMemory.FunctionalTests/Query/QueryBugsInMemoryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,119 @@ private class CustomerView19708

#endregion

#region Issue21768

[ConditionalFact]
public virtual void Using_explicit_interface_implementation_as_navigation_works()
{
using (CreateScratch<MyContext21768>((t) => { }, "21768"))
{
using var context = new MyContext21768();
Expression<Func<IBook21768, BookViewModel21768>> projection = b => new BookViewModel21768
{
FirstPage = b.FrontCover.Illustrations.FirstOrDefault(i => i.State >= IllustrationState21768.Approved) != null
? new PageViewModel21768
{
Uri = b.FrontCover.Illustrations.FirstOrDefault(i => i.State >= IllustrationState21768.Approved).Uri
}
: null,
};

var result = context.Books.Where(b => b.Id == 1).Select(projection).SingleOrDefault();
}
}

private class BookViewModel21768
{
public PageViewModel21768 FirstPage { get; set; }
}

private class PageViewModel21768
{
public string Uri { get; set; }
}

private interface IBook21768
{
public int Id { get; set; }

public IBookCover21768 FrontCover { get; }
public int FrontCoverId { get; set; }

public IBookCover21768 BackCover { get; }
public int BackCoverId { get; set; }
}

private interface IBookCover21768
{
public int Id { get; set; }
public IEnumerable<ICoverIllustration21768> Illustrations { get; }
}

private interface ICoverIllustration21768
{
public int Id { get; set; }
public IBookCover21768 Cover { get; }
public int CoverId { get; set; }
public string Uri { get; set; }
public IllustrationState21768 State { get; set; }
}

private class Book21768 : IBook21768
{
public int Id { get; set; }

public BookCover21768 FrontCover { get; set; }
public int FrontCoverId { get; set; }

public BookCover21768 BackCover { get; set; }
public int BackCoverId { get; set; }
IBookCover21768 IBook21768.FrontCover => FrontCover;
IBookCover21768 IBook21768.BackCover => BackCover;
}

private class BookCover21768 : IBookCover21768
{
public int Id { get; set; }
public ICollection<CoverIllustration21768> Illustrations { get; set; }
IEnumerable<ICoverIllustration21768> IBookCover21768.Illustrations => Illustrations;
}

private class CoverIllustration21768 : ICoverIllustration21768
{
public int Id { get; set; }
public BookCover21768 Cover { get; set; }
public int CoverId { get; set; }
public string Uri { get; set; }
public IllustrationState21768 State { get; set; }

IBookCover21768 ICoverIllustration21768.Cover => Cover;
}

private enum IllustrationState21768
{
New,
PendingApproval,
Approved,
Printed
}

private class MyContext21768 : DbContext
{
public DbSet<Book21768> Books { get; set; }
public DbSet<BookCover21768> BookCovers { get; set; }
public DbSet<CoverIllustration21768> CoverIllustrations { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseInternalServiceProvider(InMemoryFixture.DefaultServiceProvider)
.UseInMemoryDatabase("21768");
}
}

#endregion

#region SharedHelper

private static InMemoryTestStore CreateScratch<TContext>(Action<TContext> seed, string databaseName)
Expand Down
Loading

0 comments on commit 8be0230

Please sign in to comment.