2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
// <copyright file="ObjectSpanRewriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
2017-08-21 15:34:15 +00:00
// @owner Microsoft
// @backupowner Microsoft
2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Data.Common ;
using System.Data.Common.Utils ;
using System.Data.Metadata.Edm ;
using System.Data.Common.CommandTrees ;
using System.Globalization ;
using System.Data.Common.CommandTrees.ExpressionBuilder ;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Responsible for performing Relationship-span only rewrites over a Command Tree rooted
/// by the <see cref="Query"/> property. Virtual methods provide an opportunity for derived
/// classes to implement Full-span rewrites.
/// </summary>
internal class ObjectSpanRewriter
{
internal static bool EntityTypeEquals ( EntityTypeBase entityType1 , EntityTypeBase entityType2 )
{
return object . ReferenceEquals ( entityType1 , entityType2 ) ;
}
#region Private members
private int _spanCount ;
private SpanIndex _spanIndex ;
private DbExpression _toRewrite ;
private bool _relationshipSpan ;
private DbCommandTree _tree ;
private Stack < NavigationInfo > _navSources = new Stack < NavigationInfo > ( ) ;
private readonly AliasGenerator _aliasGenerator ;
#endregion
#region ' Public ' API
internal static bool TryRewrite ( DbQueryCommandTree tree , Span span , MergeOption mergeOption , AliasGenerator aliasGenerator , out DbExpression newQuery , out SpanIndex spanInfo )
{
newQuery = null ;
spanInfo = null ;
ObjectSpanRewriter rewriter = null ;
bool requiresRelationshipSpan = Span . RequiresRelationshipSpan ( mergeOption ) ;
// Potentially perform a rewrite for span.
// Note that the public 'Span' property is NOT used to retrieve the Span instance
// since this forces creation of a Span object that may not be required.
if ( span ! = null & & span . SpanList . Count > 0 )
{
rewriter = new ObjectFullSpanRewriter ( tree , tree . Query , span , aliasGenerator ) ;
}
else if ( requiresRelationshipSpan )
{
rewriter = new ObjectSpanRewriter ( tree , tree . Query , aliasGenerator ) ;
}
if ( rewriter ! = null )
{
rewriter . RelationshipSpan = requiresRelationshipSpan ;
newQuery = rewriter . RewriteQuery ( ) ;
if ( newQuery ! = null )
{
Debug . Assert ( rewriter . SpanIndex ! = null | | tree . Query . ResultType . EdmEquals ( newQuery . ResultType ) , "Query was rewritten for Span but no SpanIndex was created?" ) ;
spanInfo = rewriter . SpanIndex ;
}
}
return ( spanInfo ! = null ) ;
}
/// <summary>
/// Constructs a new ObjectSpanRewriter that will attempt to apply spanning to the specified query
/// (represented as a DbExpression) when <see cref="RewriteQuery"/> is called.
/// </summary>
/// <param name="toRewrite">A <see cref="DbExpression"/> representing the query to span.</param>
internal ObjectSpanRewriter ( DbCommandTree tree , DbExpression toRewrite , AliasGenerator aliasGenerator )
{
Debug . Assert ( toRewrite ! = null , "Expression to rewrite cannot be null" ) ;
_toRewrite = toRewrite ;
_tree = tree ;
_aliasGenerator = aliasGenerator ;
}
/// <summary>
/// Gets the metadata workspace the will be used to retrieve required metadata, for example association types.
/// </summary>
internal MetadataWorkspace Metadata { get { return _tree . MetadataWorkspace ; } }
/// <summary>
/// Gets a DbExpression representing the query that should be spanned.
/// </summary>
internal DbExpression Query { get { return _toRewrite ; } }
/// <summary>
/// Gets a value indicating whether relationship span is required (ObjectQuery sets this to 'false' for NoTracking queries).
/// </summary>
internal bool RelationshipSpan { get { return _relationshipSpan ; } set { _relationshipSpan = value ; } }
/// <summary>
/// Gets a dictionary that indicates, for a given result row type produced by a span rewrite,
/// which columns represent which association end members.
/// This dictionary is initially empty before <see cref="RewriteQuery"/> is called and will remain so
/// if no rewrites are required.
/// </summary>
internal SpanIndex SpanIndex { get { return _spanIndex ; } }
/// <summary>
/// Main 'public' entry point called by ObjectQuery.
/// </summary>
/// <returns>The rewritten version of <see cref="Query"/> if spanning was required; otherwise <c>null</c>.</returns>
internal DbExpression RewriteQuery ( )
{
DbExpression retExpr = Rewrite ( _toRewrite ) ;
if ( object . ReferenceEquals ( _toRewrite , retExpr ) )
{
return null ;
}
else
{
return retExpr ;
}
}
#endregion
#region ' Protected ' API
internal struct SpanTrackingInfo
{
public List < KeyValuePair < string , DbExpression > > ColumnDefinitions ;
public AliasGenerator ColumnNames ;
public Dictionary < int , AssociationEndMember > SpannedColumns ;
public Dictionary < AssociationEndMember , bool > FullSpannedEnds ;
}
internal SpanTrackingInfo InitializeTrackingInfo ( bool createAssociationEndTrackingInfo )
{
SpanTrackingInfo info = new SpanTrackingInfo ( ) ;
info . ColumnDefinitions = new List < KeyValuePair < string , DbExpression > > ( ) ;
info . ColumnNames = new AliasGenerator ( string . Format ( CultureInfo . InvariantCulture , "Span{0}_Column" , _spanCount ) ) ;
info . SpannedColumns = new Dictionary < int , AssociationEndMember > ( ) ;
if ( createAssociationEndTrackingInfo )
{
info . FullSpannedEnds = new Dictionary < AssociationEndMember , bool > ( ) ;
}
return info ;
}
internal virtual SpanTrackingInfo CreateEntitySpanTrackingInfo ( DbExpression expression , EntityType entityType ) { return new SpanTrackingInfo ( ) ; }
protected DbExpression Rewrite ( DbExpression expression )
{
//SQLBUDT #554182: This is special casing for expressions below which it is safe to push the span
// info without having to rebind. By pushing the span info down (i.e. possible extra projections),
// we potentially end up with simpler generated command.
switch ( expression . ExpressionKind )
{
case DbExpressionKind . Element :
return RewriteElementExpression ( ( DbElementExpression ) expression ) ;
case DbExpressionKind . Limit :
return RewriteLimitExpression ( ( DbLimitExpression ) expression ) ;
}
switch ( expression . ResultType . EdmType . BuiltInTypeKind )
{
case BuiltInTypeKind . EntityType :
return RewriteEntity ( expression , ( EntityType ) expression . ResultType . EdmType ) ;
case BuiltInTypeKind . CollectionType :
return RewriteCollection ( expression , ( CollectionType ) expression . ResultType . EdmType ) ;
case BuiltInTypeKind . RowType :
return RewriteRow ( expression , ( RowType ) expression . ResultType . EdmType ) ;
default :
return expression ;
}
}
#endregion
private void AddSpannedRowType ( RowType spannedType , TypeUsage originalType )
{
if ( null = = _spanIndex )
{
_spanIndex = new SpanIndex ( ) ;
}
_spanIndex . AddSpannedRowType ( spannedType , originalType ) ;
}
private void AddSpanMap ( RowType rowType , Dictionary < int , AssociationEndMember > columnMap )
{
if ( null = = _spanIndex )
{
_spanIndex = new SpanIndex ( ) ;
}
_spanIndex . AddSpanMap ( rowType , columnMap ) ;
}
private DbExpression RewriteEntity ( DbExpression expression , EntityType entityType )
{
// If the expression is an Entity constructor, spanning will not produce any useful results
// (null for an Entity/Ref navigation property, or an empty collection for a Collection
// of Entity/Ref navigation property) since a Ref produced from the constructed Entity
// will not indicate an Entity set, and therefore no Ref created against any Entity set
// in the container can possibly be a match for it.
if ( DbExpressionKind . NewInstance = = expression . ExpressionKind )
{
return expression ;
}
// Save the span count for later use.
_spanCount + + ;
int thisSpan = _spanCount ;
SpanTrackingInfo tracking = CreateEntitySpanTrackingInfo ( expression , entityType ) ;
// If relationship span is required then attempt to span any appropriate relationship ends.
List < KeyValuePair < AssociationEndMember , AssociationEndMember > > relationshipSpans = null ;
relationshipSpans = GetRelationshipSpanEnds ( entityType ) ;
// Is the Entity type of this expression valid as the source of at least one relationship span?
if ( relationshipSpans ! = null )
{
// If the span tracking information was not initialized by CreateEntitySpanTrackingInfo,
// then do so now as relationship span rewrites need to be tracked.
if ( null = = tracking . ColumnDefinitions )
{
tracking = InitializeTrackingInfo ( false ) ;
}
// Track column index to span information, starting at the current column count (which could be zero) plus 1.
// 1 is added because the column containing the root entity will be added later to provide column zero.
int idx = tracking . ColumnDefinitions . Count + 1 ;
// For all applicable relationship spans that were identified...
foreach ( KeyValuePair < AssociationEndMember , AssociationEndMember > relSpan in relationshipSpans )
{
// If the specified association end member was already full-spanned then the full entity
// will be returned in the query and there is no need to relationship-span this end to produce
// another result column that contains the Entity key of the full entity.
// Hence the relationship span is only added if there are no full-span columns or the full-span
// columns do not indicate that they include the target association end member of this relationship span.
if ( null = = tracking . FullSpannedEnds | |
! tracking . FullSpannedEnds . ContainsKey ( relSpan . Value ) )
{
// If the source Ref is already available, because the currently spanned Entity is
// the result of a Relationship Navigation operation from that Ref, then use the source
// Ref directly rather than introducing a new Navigation operation.
DbExpression columnDef = null ;
if ( ! TryGetNavigationSource ( relSpan . Value , out columnDef ) )
{
// Add a new column defined by the navigation required to reach the targeted association end
// and update the column -> association end map to include an entry for this new column.
DbExpression navSource = expression . GetEntityRef ( ) ;
columnDef = navSource . NavigateAllowingAllRelationshipsInSameTypeHierarchy ( relSpan . Key , relSpan . Value ) ;
}
tracking . ColumnDefinitions . Add (
new KeyValuePair < string , DbExpression > (
tracking . ColumnNames . Next ( ) ,
columnDef
)
) ;
tracking . SpannedColumns [ idx ] = relSpan . Value ;
// Increment the tracked column count
idx + + ;
}
}
}
// If no spanned columns have been added then simply return the original expression
if ( null = = tracking . ColumnDefinitions )
{
_spanCount - - ;
return expression ;
}
// Add the original entity-producing expression as the first (root) span column.
tracking . ColumnDefinitions . Insert (
0 ,
new KeyValuePair < string , DbExpression > (
string . Format ( CultureInfo . InvariantCulture , "Span{0}_SpanRoot" , thisSpan ) ,
expression
)
) ;
// Create the span row-producing NewInstanceExpression from which the span RowType can be retrieved.
DbExpression spannedExpression = DbExpressionBuilder . NewRow ( tracking . ColumnDefinitions ) ;
// Update the rowtype -> spaninfo map for the newly created row type instance.
RowType spanRowType = ( RowType ) spannedExpression . ResultType . EdmType ;
AddSpanMap ( spanRowType , tracking . SpannedColumns ) ;
// Return the rewritten expression
return spannedExpression ;
}
private DbExpression RewriteElementExpression ( DbElementExpression expression )
{
DbExpression rewrittenInput = Rewrite ( expression . Argument ) ;
if ( ! object . ReferenceEquals ( expression . Argument , rewrittenInput ) )
{
expression = rewrittenInput . Element ( ) ;
}
return expression ;
}
private DbExpression RewriteLimitExpression ( DbLimitExpression expression )
{
DbExpression rewrittenInput = Rewrite ( expression . Argument ) ;
if ( ! object . ReferenceEquals ( expression . Argument , rewrittenInput ) )
{
// Note that here we use the original expression.Limit. It is safe to do so,
// because we only allow physical paging (i.e. Limit can only be a constant or parameter)
expression = rewrittenInput . Limit ( expression . Limit ) ;
}
return expression ;
}
private DbExpression RewriteRow ( DbExpression expression , RowType rowType )
{
DbLambdaExpression lambdaExpression = expression as DbLambdaExpression ;
DbNewInstanceExpression newRow ;
if ( lambdaExpression ! = null )
{
// NOTE: We rely on the fact that today span cannot be done over queries containing DbLambdaExpressions
// created by users, because user-created expressions cannot be used for querying in O-space.
// If that were to change, pushing span beyond a LambdaExpression could cause variable name
// collisions between the variable names used in the Lambda and the names generated by the
// RelationshipNavigationVisitor.
newRow = lambdaExpression . Lambda . Body as DbNewInstanceExpression ;
}
else
{
newRow = expression as DbNewInstanceExpression ;
}
Dictionary < int , DbExpression > unmodifiedColumns = null ;
Dictionary < int , DbExpression > spannedColumns = null ;
for ( int idx = 0 ; idx < rowType . Properties . Count ; idx + + )
{
// Retrieve the property that represents the current column
EdmProperty columnProp = rowType . Properties [ idx ] ;
// Construct an expression that defines the current column.
DbExpression columnExpr = null ;
if ( newRow ! = null )
{
// For a row-constructing NewInstance expression, the corresponding argument can simply be used
columnExpr = newRow . Arguments [ idx ] ;
}
else
{
// For all other expressions the property corresponding to the column name must be retrieved
// from the row-typed expression
columnExpr = expression . Property ( columnProp . Name ) ;
}
DbExpression spannedColumn = this . Rewrite ( columnExpr ) ;
if ( ! object . ReferenceEquals ( spannedColumn , columnExpr ) )
{
// If so, then update the dictionary of column index to span information
if ( null = = spannedColumns )
{
spannedColumns = new Dictionary < int , DbExpression > ( ) ;
}
spannedColumns [ idx ] = spannedColumn ;
}
else
{
// Otherwise, update the dictionary of column index to unmodified expression
if ( null = = unmodifiedColumns )
{
unmodifiedColumns = new Dictionary < int , DbExpression > ( ) ;
}
unmodifiedColumns [ idx ] = columnExpr ;
}
}
// A new expression need only be built if at least one column was spanned
if ( null = = spannedColumns )
{
// No columns were spanned, indicate that the original expression should remain.
return expression ;
}
else
{
// At least one column was spanned, so build a new row constructor that defines the new row, including spanned columns.
List < DbExpression > columnArguments = new List < DbExpression > ( rowType . Properties . Count ) ;
List < EdmProperty > properties = new List < EdmProperty > ( rowType . Properties . Count ) ;
for ( int idx = 0 ; idx < rowType . Properties . Count ; idx + + )
{
EdmProperty columnProp = rowType . Properties [ idx ] ;
DbExpression columnDef = null ;
if ( ! spannedColumns . TryGetValue ( idx , out columnDef ) )
{
columnDef = unmodifiedColumns [ idx ] ;
}
columnArguments . Add ( columnDef ) ;
properties . Add ( new EdmProperty ( columnProp . Name , columnDef . ResultType ) ) ;
}
// Copy over any eLinq initializer metadata (if present, or null if not).
// Note that this initializer metadata does not strictly match the new row type
// that includes spanned columns, but will be correct once the object materializer
// has interpreted the query results to produce the correct value for each colum.
RowType rewrittenRow = new RowType ( properties , rowType . InitializerMetadata ) ;
TypeUsage rewrittenRowTypeUsage = TypeUsage . Create ( rewrittenRow ) ;
DbExpression rewritten = rewrittenRowTypeUsage . New ( columnArguments ) ;
// SQLBUDT #554182: If we insert a new projection we should should make sure to
// not interfere with the nullability of the input.
// In particular, if the input row is null and we construct a new row as a projection over its columns
// we would get a row consisting of nulls, instead of a null row.
// Thus, given an input X, we rewritte it as: if (X is null) then NULL else rewritten.
if ( newRow = = null )
{
DbExpression condition = DbExpressionBuilder . CreateIsNullExpressionAllowingRowTypeArgument ( expression ) ;
DbExpression nullExpression = DbExpressionBuilder . Null ( rewrittenRowTypeUsage ) ;
rewritten = DbExpressionBuilder . Case (
new List < DbExpression > ( new DbExpression [ ] { condition } ) ,
new List < DbExpression > ( new DbExpression [ ] { nullExpression } ) ,
rewritten ) ;
}
// Add an entry to the spanned row type => original row type map for the new row type.
AddSpannedRowType ( rewrittenRow , expression . ResultType ) ;
if ( lambdaExpression ! = null & & newRow ! = null )
{
rewritten = DbLambda . Create ( rewritten , lambdaExpression . Lambda . Variables ) . Invoke ( lambdaExpression . Arguments ) ;
}
return rewritten ;
}
}
private DbExpression RewriteCollection ( DbExpression expression , CollectionType collectionType )
{
DbExpression target = expression ;
// If the collection expression is a project expression, get a strongly typed reference to it for later use.
DbProjectExpression project = null ;
if ( DbExpressionKind . Project = = expression . ExpressionKind )
{
project = ( DbProjectExpression ) expression ;
target = project . Input . Expression ;
}
// If Relationship span is enabled and the source of this collection is (directly or indirectly)
// a RelationshipNavigation operation, it may be possible to optimize the relationship span rewrite
// for the Entities produced by the navigation.
NavigationInfo navInfo = null ;
if ( this . RelationshipSpan )
{
// Attempt to find a RelationshipNavigationExpression in the collection-defining expression
target = RelationshipNavigationVisitor . FindNavigationExpression ( target , _aliasGenerator , out navInfo ) ;
}
// If a relationship navigation expression defines this collection, make the Ref that is the navigation source
// and the source association end available for possible use when the projection over the collection is rewritten.
if ( navInfo ! = null )
{
this . EnterNavigationCollection ( navInfo ) ;
}
else
{
// Otherwise, add a null navigation info instance to the stack to indicate that relationship navigation
// cannot be optimized for the entities produced by this collection expression (if it is a collection of entities).
this . EnterCollection ( ) ;
}
// If the expression is already a DbProjectExpression then simply visit the projection,
// instead of introducing another projection over the existing one.
DbExpression result = expression ;
if ( project ! = null )
{
DbExpression newProjection = this . Rewrite ( project . Projection ) ;
if ( ! object . ReferenceEquals ( project . Projection , newProjection ) )
{
result = target . BindAs ( project . Input . VariableName ) . Project ( newProjection ) ;
}
}
else
{
// This is not a recognized special case, so simply add the span projection over the original
// collection-producing expression, if it is required.
DbExpressionBinding collectionBinding = target . BindAs ( _aliasGenerator . Next ( ) ) ;
DbExpression projection = collectionBinding . Variable ;
DbExpression spannedProjection = this . Rewrite ( projection ) ;
if ( ! object . ReferenceEquals ( projection , spannedProjection ) )
{
result = collectionBinding . Project ( spannedProjection ) ;
}
}
// Remove any navigation information from scope, if it was added
this . ExitCollection ( ) ;
// If a navigation expression defines this collection and its navigation information was used to
// short-circuit relationship span rewrites, then enclose the entire rewritten expression in a
// Lambda binding that brings the source Ref of the navigation operation into scope. This ref is
// refered to by VariableReferenceExpressions in the original navigation expression as well as any
// short-circuited relationship span columns in the rewritten expression.
if ( navInfo ! = null & & navInfo . InUse )
{
// Create a Lambda function that binds the original navigation source expression under the variable name
// used in the navigation expression and the relationship span columns, and which has its Lambda body
// defined by the rewritten collection expression.
List < DbVariableReferenceExpression > formals = new List < DbVariableReferenceExpression > ( 1 ) ;
formals . Add ( navInfo . SourceVariable ) ;
List < DbExpression > args = new List < DbExpression > ( 1 ) ;
args . Add ( navInfo . Source ) ;
result = DbExpressionBuilder . Invoke ( DbExpressionBuilder . Lambda ( result , formals ) , args ) ;
}
// Return the (possibly rewritten) collection expression.
return result ;
}
private void EnterCollection ( )
{
_navSources . Push ( null ) ;
}
private void EnterNavigationCollection ( NavigationInfo info )
{
_navSources . Push ( info ) ;
}
private void ExitCollection ( )
{
_navSources . Pop ( ) ;
}
private bool TryGetNavigationSource ( AssociationEndMember wasSourceNowTargetEnd , out DbExpression source )
{
source = null ;
NavigationInfo info = null ;
if ( _navSources . Count > 0 )
{
info = _navSources . Peek ( ) ;
if ( info ! = null & & ! object . ReferenceEquals ( wasSourceNowTargetEnd , info . SourceEnd ) )
{
info = null ;
}
}
if ( info ! = null )
{
source = info . SourceVariable ;
info . InUse = true ;
return true ;
}
else
{
return false ;
}
}
/// <summary>
/// Gathers the applicable { from, to } relationship end pairings for the specified entity type.
/// Note that it is possible for both { x, y } and { y, x } - where x and y are relationship ends -
/// to be returned if the relationship is symmetric (in the sense that it has multiplicity of at
/// most one in each direction and the type of each end is Ref to the same Entity type, or a supertype).
/// </summary>
/// <param name="entityType">The Entity type for which the applicable { from, to } end pairings should be retrieved.</param>
/// <returns>
/// A List of association end members pairings that describes the available { from, to } navigations
/// for the specified Entity type that are valid for Relationship Span; or <c>null</c> if no such pairings exist.
/// </returns>
private List < KeyValuePair < AssociationEndMember , AssociationEndMember > > GetRelationshipSpanEnds ( EntityType entityType )
{
// The list to be returned; initially null.
List < KeyValuePair < AssociationEndMember , AssociationEndMember > > retList = null ;
// If relationship span is not enabled then do not attempt to retrieve the applicable navigations.
if ( _relationshipSpan )
{
// Consider all Association types...
foreach ( AssociationType association in _tree . MetadataWorkspace . GetItems < AssociationType > ( DataSpace . CSpace ) )
{
// ... which have exactly two ends
if ( 2 = = association . AssociationEndMembers . Count )
{
AssociationEndMember end0 = association . AssociationEndMembers [ 0 ] ;
AssociationEndMember end1 = association . AssociationEndMembers [ 1 ] ;
// If end0 -> end1 is valid for relationship span then add { end0, end1 }
// to the list of end pairings.
if ( IsValidRelationshipSpan ( entityType , association , end0 , end1 ) )
{
// If the list has not been instantiated, do so now.
if ( null = = retList )
{
retList = new List < KeyValuePair < AssociationEndMember , AssociationEndMember > > ( ) ;
}
retList . Add ( new KeyValuePair < AssociationEndMember , AssociationEndMember > ( end0 , end1 ) ) ;
}
// Similarly if the inverse navigation is also or instead valid for relationship span
// then add the { end1, end0 } pairing to the list of valid end pairings.
if ( IsValidRelationshipSpan ( entityType , association , end1 , end0 ) )
{
// Again, if the list has not been instantiated, do so now.
if ( null = = retList )
{
retList = new List < KeyValuePair < AssociationEndMember , AssociationEndMember > > ( ) ;
}
retList . Add ( new KeyValuePair < AssociationEndMember , AssociationEndMember > ( end1 , end0 ) ) ;
}
}
}
}
// Return the list (which may still be null at this point)
return retList ;
}
/// <summary>
/// Determines whether the specified { from, to } relationship end pairing represents a navigation that is
/// valid for a relationship span sourced by an instance of the specified entity type.
/// </summary>
/// <param name="compareType">The Entity type which valid 'from' ends must reference (or a supertype of that Entity type)</param>
/// <param name="associationType">The Association type to consider.</param>
/// <param name="fromEnd">The candidate 'from' end, which will be checked based on the Entity type it references</param>
/// <param name="toEnd">The candidate 'to' end, which will be checked base on the upper bound of its multiplicity</param>
/// <returns>
/// <c>True</c> if the end pairing represents a valid navigation from an instance of the specified entity type
/// to an association end with a multiplicity upper bound of at most 1; otherwise <c>false</c>
/// </returns>
private static bool IsValidRelationshipSpan ( EntityType compareType , AssociationType associationType , AssociationEndMember fromEnd , AssociationEndMember toEnd )
{
// Only a relationship end with a multiplicity of AT MOST one may be
// considered as the 'to' end, so that the cardinality of the result
// of the relationship span has an upper bound of 1.
// Therefore ends with RelationshipMultiplicity of EITHER One OR ZeroOrOne
// are the only ends that should be considered as target ends.
// Note that a relationship span can be sourced by an Entity that is of the same type
// as the Entity type referenced by the 'from' end OR any type in the same branch of
// the type hierarchy.
//
// For example, in the following hierarchy:
//
// A (*<-->?) AOwner
// |_B (*<-->1) BOwner
// |_A1 (*<-->?) A1Owner
// |_A2
// |_A3_1 (1<-->?) A3_1Owner
// |_A3_2 (*<-->1) A3_2Owner
//
// An instance of 'A' would need ALL the 'AOwner', 'BOwner', 'A1Owner', 'A3_1Owner' and 'A3_2Owner' ends
// spanned in because an instance of 'A' could actually be an instance of A, B, A1, A2, A3_1 or A3_2.
// An instance of 'B' would only need 'AOwner' and 'BOwner' spanned in.
// An instance of A2 would need 'AOwner', 'A1Owner', 'A3_1Owner' and 'A3_2Owner' spanned in.
// An instance of A3_1 would only need 'AOwner', 'A1Owner' and 'A3_1Owner' spanned in.
//
// In general, the rule for relationship span is:
// - 'To' end cardinality AT MOST one
// AND
// - Referenced Entity type of 'From' end is equal to instance Entity type
// OR
// - Referenced Entity type of 'From' end is a supertype of instance Entity type
// OR
// - Referenced Entity type of 'From' end is a subtype of instance Entity type
// (this follows from the fact that an instance of 'A' may be an instance of any of its derived types.
// Navigation for a subtype relationship will return null if the Entity instance navigation source
// is not actually of the required subtype).
//
if ( ! associationType . IsForeignKey & &
( RelationshipMultiplicity . One = = toEnd . RelationshipMultiplicity | |
RelationshipMultiplicity . ZeroOrOne = = toEnd . RelationshipMultiplicity ) )
{
EntityType fromEntityType = ( EntityType ) ( ( RefType ) fromEnd . TypeUsage . EdmType ) . ElementType ;
return ( ObjectSpanRewriter . EntityTypeEquals ( compareType , fromEntityType ) | |
TypeSemantics . IsSubTypeOf ( compareType , fromEntityType ) | |
TypeSemantics . IsSubTypeOf ( fromEntityType , compareType ) ) ;
}
return false ;
}
#region Nested types used for Relationship span over Relationship Navigation optimizations
private class NavigationInfo
{
private readonly DbRelationshipNavigationExpression _original ;
private readonly DbRelationshipNavigationExpression _rewritten ;
private DbVariableReferenceExpression _sourceRef ;
private AssociationEndMember _sourceEnd ;
private DbExpression _source ;
public NavigationInfo ( DbRelationshipNavigationExpression originalNavigation , DbRelationshipNavigationExpression rewrittenNavigation )
{
Debug . Assert ( originalNavigation ! = null , "originalNavigation cannot be null" ) ;
Debug . Assert ( rewrittenNavigation ! = null , "rewrittenNavigation cannot be null" ) ;
this . _original = originalNavigation ;
this . _rewritten = rewrittenNavigation ;
this . _sourceEnd = ( AssociationEndMember ) originalNavigation . NavigateFrom ;
this . _sourceRef = ( DbVariableReferenceExpression ) rewrittenNavigation . NavigationSource ;
this . _source = originalNavigation . NavigationSource ;
}
public bool InUse ;
public AssociationEndMember SourceEnd { get { return _sourceEnd ; } }
public DbExpression Source { get { return _source ; } }
public DbVariableReferenceExpression SourceVariable { get { return _sourceRef ; } }
}
private class RelationshipNavigationVisitor : DefaultExpressionVisitor
{
internal static DbExpression FindNavigationExpression ( DbExpression expression , AliasGenerator aliasGenerator , out NavigationInfo navInfo )
{
Debug . Assert ( TypeSemantics . IsCollectionType ( expression . ResultType ) , "Non-collection input to projection?" ) ;
navInfo = null ;
TypeUsage elementType = ( ( CollectionType ) expression . ResultType . EdmType ) . TypeUsage ;
if ( ! TypeSemantics . IsEntityType ( elementType ) & & ! TypeSemantics . IsReferenceType ( elementType ) )
{
return expression ;
}
RelationshipNavigationVisitor visitor = new RelationshipNavigationVisitor ( aliasGenerator ) ;
DbExpression rewrittenExpression = visitor . Find ( expression ) ;
if ( ! object . ReferenceEquals ( expression , rewrittenExpression ) )
{
Debug . Assert ( visitor . _original ! = null & & visitor . _rewritten ! = null , "Expression was rewritten but no navigation was found?" ) ;
navInfo = new NavigationInfo ( visitor . _original , visitor . _rewritten ) ;
return rewrittenExpression ;
}
else
{
return expression ;
}
}
private readonly AliasGenerator _aliasGenerator ;
private DbRelationshipNavigationExpression _original ;
private DbRelationshipNavigationExpression _rewritten ;
private RelationshipNavigationVisitor ( AliasGenerator aliasGenerator )
{
_aliasGenerator = aliasGenerator ;
}
private DbExpression Find ( DbExpression expression )
{
return this . VisitExpression ( expression ) ;
}
protected override DbExpression VisitExpression ( DbExpression expression )
{
switch ( expression . ExpressionKind )
{
case DbExpressionKind . RelationshipNavigation :
case DbExpressionKind . Distinct :
case DbExpressionKind . Filter :
case DbExpressionKind . Limit :
case DbExpressionKind . OfType :
case DbExpressionKind . Project :
case DbExpressionKind . Sort :
case DbExpressionKind . Skip :
return base . VisitExpression ( expression ) ;
default :
return expression ;
}
}
public override DbExpression Visit ( DbRelationshipNavigationExpression expression )
{
this . _original = expression ;
// Ensure a unique variable name when the expression is used in a command tree
string varName = _aliasGenerator . Next ( ) ;
DbVariableReferenceExpression sourceRef = new DbVariableReferenceExpression ( expression . NavigationSource . ResultType , varName ) ;
this . _rewritten = sourceRef . Navigate ( expression . NavigateFrom , expression . NavigateTo ) ;
return this . _rewritten ;
}
// For Distinct, Limit, OfType there is no need to override the base visitor behavior.
public override DbExpression Visit ( DbFilterExpression expression )
{
// Only consider the Filter input
DbExpression found = Find ( expression . Input . Expression ) ;
if ( ! object . ReferenceEquals ( found , expression . Input . Expression ) )
{
return found . BindAs ( expression . Input . VariableName ) . Filter ( expression . Predicate ) ;
}
else
{
return expression ;
}
}
public override DbExpression Visit ( DbProjectExpression expression )
{
// Only allowed cases:
// SELECT Deref(x) FROM <expression> AS x
// SELECT x FROM <expression> as x
DbExpression testExpr = expression . Projection ;
if ( DbExpressionKind . Deref = = testExpr . ExpressionKind )
{
testExpr = ( ( DbDerefExpression ) testExpr ) . Argument ;
}
if ( DbExpressionKind . VariableReference = = testExpr . ExpressionKind )
{
DbVariableReferenceExpression varRef = ( DbVariableReferenceExpression ) testExpr ;
if ( varRef . VariableName . Equals ( expression . Input . VariableName , StringComparison . Ordinal ) )
{
DbExpression found = Find ( expression . Input . Expression ) ;
if ( ! object . ReferenceEquals ( found , expression . Input . Expression ) )
{
return found . BindAs ( expression . Input . VariableName ) . Project ( expression . Projection ) ;
}
}
}
return expression ;
}
public override DbExpression Visit ( DbSortExpression expression )
{
DbExpression found = Find ( expression . Input . Expression ) ;
if ( ! object . ReferenceEquals ( found , expression . Input . Expression ) )
{
return found . BindAs ( expression . Input . VariableName ) . Sort ( expression . SortOrder ) ;
}
else
{
return expression ;
}
}
public override DbExpression Visit ( DbSkipExpression expression )
{
DbExpression found = Find ( expression . Input . Expression ) ;
if ( ! object . ReferenceEquals ( found , expression . Input . Expression ) )
{
return found . BindAs ( expression . Input . VariableName ) . Skip ( expression . SortOrder , expression . Count ) ;
}
else
{
return expression ;
}
}
}
#endregion
}
}