9
9
using System . Collections . Generic ;
10
10
using System . Diagnostics . CodeAnalysis ;
11
11
using System . Diagnostics . Contracts ;
12
+ using System . Diagnostics . Tracing ;
12
13
using System . Linq ;
13
14
using System . Linq . Expressions ;
14
15
using System . Reflection ;
16
+ using System . Reflection . Emit ;
15
17
using Microsoft . AspNetCore . OData . Common ;
16
18
using Microsoft . AspNetCore . OData . Edm ;
17
19
using Microsoft . AspNetCore . OData . Query . Container ;
@@ -29,6 +31,8 @@ namespace Microsoft.AspNetCore.OData.Query.Expressions
29
31
/// </summary>
30
32
public class SelectExpandBinder : QueryBinder , ISelectExpandBinder
31
33
{
34
+ private string queryProvider ;
35
+
32
36
/// <summary>
33
37
/// Initializes a new instance of the <see cref="SelectExpandBinder" /> class.
34
38
/// Select and Expand binder depends on <see cref="IFilterBinder"/> and <see cref="IOrderByBinder"/> to process inner $filter and $orderby.
@@ -80,9 +84,12 @@ public virtual Expression BindSelectExpand(SelectExpandClause selectExpandClause
80
84
IEdmNavigationSource navigationSource = context . NavigationSource ;
81
85
ParameterExpression source = context . CurrentParameter ;
82
86
87
+ queryProvider = context . QueryProvider ;
88
+
83
89
// expression looks like -> new Wrapper { Instance = source , Properties = "...", Container = new PropertyContainer { ... } }
84
90
Expression projectionExpression = ProjectElement ( context , source , selectExpandClause , structuredType , navigationSource ) ;
85
91
92
+
86
93
// expression looks like -> source => new Wrapper { Instance = source .... }
87
94
LambdaExpression projectionLambdaExpression = Expression . Lambda ( projectionExpression , source ) ;
88
95
@@ -368,9 +375,23 @@ internal Expression ProjectElement(QueryBinderContext context, Expression source
368
375
bool isSelectedAll = IsSelectAll ( selectExpandClause ) ;
369
376
if ( isSelectedAll )
370
377
{
378
+ // If we have an EF query provider, then we remove the non-structural properties. The reason for this is to avoid
379
+ // Creating an expression that will generate duplicate LEFT joins when a LEFT join already exists in the IQueryable
380
+ // as observed here: https://github.com/OData/AspNetCoreOData/issues/497
381
+
382
+ Expression updatedExpression = null ;
383
+ if ( IsEfQueryProvider ( ) )
384
+ {
385
+ updatedExpression = SelectExpandBinder . RemoveNonStructucalProperties ( source , structuredType ) ;
386
+ }
387
+ else
388
+ {
389
+ updatedExpression = source ;
390
+ }
391
+
371
392
// Initialize property 'Instance' on the wrapper class
372
393
wrapperProperty = wrapperType . GetProperty ( "Instance" ) ;
373
- wrapperTypeMemberAssignments . Add ( Expression . Bind ( wrapperProperty , source ) ) ;
394
+ wrapperTypeMemberAssignments . Add ( Expression . Bind ( wrapperProperty , updatedExpression ) ) ;
374
395
375
396
wrapperProperty = wrapperType . GetProperty ( "UseInstanceForProperties" ) ;
376
397
wrapperTypeMemberAssignments . Add ( Expression . Bind ( wrapperProperty , Expression . Constant ( true ) ) ) ;
@@ -427,6 +448,51 @@ internal Expression ProjectElement(QueryBinderContext context, Expression source
427
448
return Expression . MemberInit ( Expression . New ( wrapperType ) , wrapperTypeMemberAssignments ) ;
428
449
}
429
450
451
+ // Generates the expression
452
+ // { Instance = new Customer() {Id = $it.Id, Name= $it.Name}}
453
+ private static Expression RemoveNonStructucalProperties ( Expression source , IEdmStructuredType structuredType )
454
+ {
455
+ Expression updatedSource = null ;
456
+
457
+ Type elementType = source . Type ;
458
+ IEnumerable < IEdmStructuralProperty > structuralProperties = structuredType . StructuralProperties ( ) ;
459
+
460
+ PropertyInfo [ ] props = elementType . GetProperties ( ) ;
461
+ List < MemberBinding > bindings = new List < MemberBinding > ( ) ;
462
+
463
+ foreach ( PropertyInfo prop in props )
464
+ {
465
+ foreach ( var sp in structuralProperties )
466
+ {
467
+ if ( sp . Name == prop . Name )
468
+ {
469
+ MemberExpression propertyExpression = Expression . Property ( source , prop ) ;
470
+ bindings . Add ( Expression . Bind ( prop , propertyExpression ) ) ;
471
+ break ;
472
+ }
473
+ }
474
+ }
475
+
476
+ NewExpression newExpression = Expression . New ( source . Type ) ;
477
+ updatedSource = Expression . MemberInit ( newExpression , bindings ) ;
478
+
479
+ return updatedSource ;
480
+ }
481
+
482
+ private bool IsEfQueryProvider ( )
483
+ {
484
+ if ( queryProvider != null &&
485
+ queryProvider == HandleNullPropagationOptionHelper . EntityFrameworkQueryProviderNamespace ||
486
+ queryProvider == HandleNullPropagationOptionHelper . ObjectContextQueryProviderNamespaceEF5 ||
487
+ queryProvider == HandleNullPropagationOptionHelper . ObjectContextQueryProviderNamespaceEF6 ||
488
+ queryProvider == HandleNullPropagationOptionHelper . ObjectContextQueryProviderNamespaceEFCore2 )
489
+ {
490
+ return true ;
491
+ }
492
+
493
+ return false ;
494
+ }
495
+
430
496
private static bool ParseComputedDynamicProperties ( QueryBinderContext context , IList < DynamicPathSegment > dynamicPathSegments , bool isSelectedAll ,
431
497
out IList < string > computedProperties )
432
498
{
0 commit comments