//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Security; using System.Security.Permissions; using System.Data.Metadata.Edm; using System.Data.Objects.DataClasses; using System.Collections; namespace System.Data.Objects.Internal { /// /// Defines and injects behavior into proxy class Type definitions /// to allow navigation properties to lazily load their references or collection elements. /// internal sealed class LazyLoadBehavior { /// /// Return an expression tree that represents the actions required to load the related end /// associated with the intercepted proxy member. /// /// /// EdmMember that specifies the member to be intercepted. /// /// /// PropertyInfo that specifies the CLR property to be intercepted. /// /// /// ParameterExpression that represents the proxy object. /// /// /// ParameterExpression that represents the proxied property value. /// /// The Func that retrieves the wrapper from a proxy /// /// Expression tree that encapsulates lazy loading behavior for the supplied member, /// or null if the expression tree could not be constructed. /// internal static Func GetInterceptorDelegate(EdmMember member, Func getEntityWrapperDelegate) where TProxy : class where TItem : class { Func interceptorDelegate = (proxy, item) => true; Debug.Assert(member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty, "member should represent a navigation property"); if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty) { NavigationProperty navProperty = (NavigationProperty)member; RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity; // Given the proxy and item parameters, construct one of the following expressions: // // For collections: // LazyLoadBehavior.LoadCollection(collection, "relationshipName", "targetRoleName", proxy._entityWrapperField) // // For entity references: // LazyLoadBehavior.LoadReference(item, "relationshipName", "targetRoleName", proxy._entityWrapperField) // // Both of these expressions return an object of the same type as the first parameter to LoadXYZ method. // In many cases, this will be the first parameter. if (multiplicity == RelationshipMultiplicity.Many) { interceptorDelegate = (proxy, item) => LoadProperty(item, navProperty.RelationshipType.Identity, navProperty.ToEndMember.Identity, false, getEntityWrapperDelegate(proxy)); } else { interceptorDelegate = (proxy, item) => LoadProperty(item, navProperty.RelationshipType.Identity, navProperty.ToEndMember.Identity, true, getEntityWrapperDelegate(proxy)); } } return interceptorDelegate; } /// /// Determine if the specified member is compatible with lazy loading. /// /// /// OSpace EntityType representing a type that may be proxied. /// /// /// Member of the to be examined. /// /// /// True if the member is compatible with lazy loading; otherwise false. /// /// /// To be compatible with lazy loading, /// a member must meet the criteria for being able to be proxied (defined elsewhere), /// and must be a navigation property. /// In addition, for relationships with a multiplicity of Many, /// the property type must be an implementation of ICollection<T>. /// internal static bool IsLazyLoadCandidate(EntityType ospaceEntityType, EdmMember member) { Debug.Assert(ospaceEntityType.DataSpace == DataSpace.OSpace, "ospaceEntityType.DataSpace must be OSpace"); bool isCandidate = false; if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty) { NavigationProperty navProperty = (NavigationProperty)member; RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity; PropertyInfo propertyInfo = EntityUtil.GetTopProperty(ospaceEntityType.ClrType, member.Name); Debug.Assert(propertyInfo != null, "Should have found lazy loading property"); Type propertyValueType = propertyInfo.PropertyType; if (multiplicity == RelationshipMultiplicity.Many) { Type elementType; isCandidate = EntityUtil.TryGetICollectionElementType(propertyValueType, out elementType); } else if (multiplicity == RelationshipMultiplicity.One || multiplicity == RelationshipMultiplicity.ZeroOrOne) { // This is an EntityReference property. isCandidate = true; } } return isCandidate; } /// /// Method called by proxy interceptor delegate to provide lazy loading behavior for navigation properties. /// /// property type /// The property value whose associated relationship is to be loaded. /// String name of the relationship. /// String name of the related end to be loaded for the relationship specified by . /// Entity wrapper object used to retrieve RelationshipManager for the proxied entity. /// /// True if the value instance was mutated and can be returned /// False if the class should refetch the value because the instance has changed /// private static bool LoadProperty(TItem propertyValue, string relationshipName, string targetRoleName, bool mustBeNull, object wrapperObject) where TItem : class { // Only attempt to load collection if: // // 1. Collection is non-null. // 2. ObjectContext.ContextOptions.LazyLoadingEnabled is true // 3. A non-null RelationshipManager can be retrieved (this is asserted). // 4. The EntityCollection is not already loaded. Debug.Assert(wrapperObject == null || wrapperObject is IEntityWrapper, "wrapperObject must be an IEntityWrapper"); IEntityWrapper wrapper = (IEntityWrapper)wrapperObject; // We want an exception if the cast fails. if (wrapper != null && wrapper.Context != null) { RelationshipManager relationshipManager = wrapper.RelationshipManager; Debug.Assert(relationshipManager != null, "relationshipManager should be non-null"); if (relationshipManager != null && (!mustBeNull || propertyValue == null)) { RelatedEnd relatedEnd = relationshipManager.GetRelatedEndInternal(relationshipName, targetRoleName); relatedEnd.DeferredLoad(); } } return propertyValue != null; } } }