From 94cb8f3caee518a2e4ddd45c4df376ea936b6359 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Wed, 28 Aug 2019 15:54:02 -0700 Subject: [PATCH] InMemory: Support for DefaultIfEmpty Part of #16963 --- .../Query/Internal/InMemoryQueryExpression.cs | 56 ++++++++++++++++++- ...yableMethodTranslatingExpressionVisitor.cs | 12 +++- src/EFCore/Query/EntityMaterializerSource.cs | 2 +- .../Query/AsyncSimpleQueryInMemoryTest.cs | 36 ------------ .../Query/SimpleQueryInMemoryTest.cs | 55 +++--------------- 5 files changed, 76 insertions(+), 85 deletions(-) diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 542d58dcc93..5de9275fde9 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -252,7 +252,61 @@ public virtual void PushdownIntoSubquery() } } - private IPropertyBase InferPropertyFromInner(Expression expression) + public virtual void ApplyDefaultIfEmpty() + { + if (_valueBufferSlots.Count != 0) + { + throw new InvalidOperationException("Cannot Apply DefaultIfEmpty after ClientProjection."); + } + + var result = new Dictionary(); + foreach (var keyValuePair in _projectionMapping) + { + if (keyValuePair.Value is EntityProjectionExpression entityProjection) + { + var map = new Dictionary(); + foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) + { + var index = AddToProjection(entityProjection.BindProperty(property)); + map[property] = CreateReadValueExpression(property.ClrType.MakeNullable(), index, property); + } + result[keyValuePair.Key] = new EntityProjectionExpression(entityProjection.EntityType, map); + } + else + { + var index = AddToProjection(keyValuePair.Value); + result[keyValuePair.Key] = CreateReadValueExpression( + keyValuePair.Value.Type.MakeNullable(), index, InferPropertyFromInner(keyValuePair.Value)); + } + } + + _projectionMapping = result; + + var selectorLambda = Lambda( + New( + _valueBufferConstructor, + NewArrayInit( + typeof(object), + _valueBufferSlots + .Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e))), + CurrentParameter); + + _groupingParameter = null; + + ServerQueryExpression = Call( + InMemoryLinqOperatorProvider.Select.MakeGenericMethod(ServerQueryExpression.Type.TryGetSequenceType(), typeof(ValueBuffer)), + ServerQueryExpression, + selectorLambda); + + ServerQueryExpression = Call( + InMemoryLinqOperatorProvider.DefaultIfEmptyWithArgument.MakeGenericMethod(typeof(ValueBuffer)), + ServerQueryExpression, + New(_valueBufferConstructor, NewArrayInit(typeof(object), Enumerable.Repeat(Constant(null), _valueBufferSlots.Count)))); + + _valueBufferSlots.Clear(); + } + + private static IPropertyBase InferPropertyFromInner(Expression expression) { if (expression is MethodCallExpression methodCallExpression && methodCallExpression.Method.IsGenericMethod diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index 7329a3e2b16..1944ed1f7ff 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -184,7 +184,17 @@ protected override ShapedQueryExpression TranslateCount(ShapedQueryExpression so } protected override ShapedQueryExpression TranslateDefaultIfEmpty(ShapedQueryExpression source, Expression defaultValue) - => null; + { + if (defaultValue == null) + { + ((InMemoryQueryExpression)source.QueryExpression).ApplyDefaultIfEmpty(); + source.ShaperExpression = MarkShaperNullable(source.ShaperExpression); + + return source; + } + + return null; + } protected override ShapedQueryExpression TranslateDistinct(ShapedQueryExpression source) { diff --git a/src/EFCore/Query/EntityMaterializerSource.cs b/src/EFCore/Query/EntityMaterializerSource.cs index 76b5b77c263..d4cbb842cac 100644 --- a/src/EFCore/Query/EntityMaterializerSource.cs +++ b/src/EFCore/Query/EntityMaterializerSource.cs @@ -52,7 +52,7 @@ public static readonly MethodInfo TryReadValueMethod [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TValue TryReadValue( in ValueBuffer valueBuffer, int index, IPropertyBase property) - => (TValue)valueBuffer[index]; + => valueBuffer[index] is TValue value ? value : default; public virtual Expression CreateMaterializeExpression( IEntityType entityType, diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs index 386bec964c3..295108eb638 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/AsyncSimpleQueryInMemoryTest.cs @@ -13,41 +13,5 @@ public AsyncSimpleQueryInMemoryTest(NorthwindQueryInMemoryFixture