1 //---------------------------------------------------------------------
2 // <copyright file="LazyLoadedCollectionBehavior.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
11 using System.Collections.Generic;
12 using System.Diagnostics;
14 using System.Linq.Expressions;
15 using System.Reflection;
16 using System.Reflection.Emit;
17 using System.Security;
18 using System.Security.Permissions;
19 using System.Data.Metadata.Edm;
20 using System.Data.Objects.DataClasses;
21 using System.Collections;
23 namespace System.Data.Objects.Internal
26 /// Defines and injects behavior into proxy class Type definitions
27 /// to allow navigation properties to lazily load their references or collection elements.
29 internal sealed class LazyLoadBehavior
32 /// Return an expression tree that represents the actions required to load the related end
33 /// associated with the intercepted proxy member.
35 /// <param name="member">
36 /// EdmMember that specifies the member to be intercepted.
38 /// <param name="property">
39 /// PropertyInfo that specifies the CLR property to be intercepted.
41 /// <param name="proxyParameter">
42 /// ParameterExpression that represents the proxy object.
44 /// <param name="itemParameter">
45 /// ParameterExpression that represents the proxied property value.
47 /// <param name="getEntityWrapperDelegate">The Func that retrieves the wrapper from a proxy</param>
49 /// Expression tree that encapsulates lazy loading behavior for the supplied member,
50 /// or null if the expression tree could not be constructed.
52 internal static Func<TProxy, TItem, bool> GetInterceptorDelegate<TProxy, TItem>(EdmMember member, Func<object, object> getEntityWrapperDelegate)
56 Func<TProxy, TItem, bool> interceptorDelegate = (proxy, item) => true;
58 Debug.Assert(member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty, "member should represent a navigation property");
59 if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty)
61 NavigationProperty navProperty = (NavigationProperty)member;
62 RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity;
64 // Given the proxy and item parameters, construct one of the following expressions:
67 // LazyLoadBehavior.LoadCollection(collection, "relationshipName", "targetRoleName", proxy._entityWrapperField)
69 // For entity references:
70 // LazyLoadBehavior.LoadReference(item, "relationshipName", "targetRoleName", proxy._entityWrapperField)
72 // Both of these expressions return an object of the same type as the first parameter to LoadXYZ method.
73 // In many cases, this will be the first parameter.
75 if (multiplicity == RelationshipMultiplicity.Many)
77 interceptorDelegate = (proxy, item) => LoadProperty<TItem>(item,
78 navProperty.RelationshipType.Identity,
79 navProperty.ToEndMember.Identity,
81 getEntityWrapperDelegate(proxy));
85 interceptorDelegate = (proxy, item) => LoadProperty<TItem>(item,
86 navProperty.RelationshipType.Identity,
87 navProperty.ToEndMember.Identity,
89 getEntityWrapperDelegate(proxy));
93 return interceptorDelegate;
97 /// Determine if the specified member is compatible with lazy loading.
99 /// <param name="ospaceEntityType">
100 /// OSpace EntityType representing a type that may be proxied.
102 /// <param name="member">
103 /// Member of the <paramref name="ospaceEntityType" /> to be examined.
106 /// True if the member is compatible with lazy loading; otherwise false.
109 /// To be compatible with lazy loading,
110 /// a member must meet the criteria for being able to be proxied (defined elsewhere),
111 /// and must be a navigation property.
112 /// In addition, for relationships with a multiplicity of Many,
113 /// the property type must be an implementation of ICollection<T>.
115 internal static bool IsLazyLoadCandidate(EntityType ospaceEntityType, EdmMember member)
117 Debug.Assert(ospaceEntityType.DataSpace == DataSpace.OSpace, "ospaceEntityType.DataSpace must be OSpace");
119 bool isCandidate = false;
121 if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty)
123 NavigationProperty navProperty = (NavigationProperty)member;
124 RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity;
126 PropertyInfo propertyInfo = EntityUtil.GetTopProperty(ospaceEntityType.ClrType, member.Name);
127 Debug.Assert(propertyInfo != null, "Should have found lazy loading property");
128 Type propertyValueType = propertyInfo.PropertyType;
130 if (multiplicity == RelationshipMultiplicity.Many)
133 isCandidate = EntityUtil.TryGetICollectionElementType(propertyValueType, out elementType);
135 else if (multiplicity == RelationshipMultiplicity.One || multiplicity == RelationshipMultiplicity.ZeroOrOne)
137 // This is an EntityReference property.
146 /// Method called by proxy interceptor delegate to provide lazy loading behavior for navigation properties.
148 /// <typeparam name="TItem">property type</typeparam>
149 /// <param name="propertyValue">The property value whose associated relationship is to be loaded.</param>
150 /// <param name="relationshipName">String name of the relationship.</param>
151 /// <param name="targetRoleName">String name of the related end to be loaded for the relationship specified by <paramref name="relationshipName"/>.</param>
152 /// <param name="wrapperObject">Entity wrapper object used to retrieve RelationshipManager for the proxied entity.</param>
154 /// True if the value instance was mutated and can be returned
155 /// False if the class should refetch the value because the instance has changed
157 private static bool LoadProperty<TItem>(TItem propertyValue, string relationshipName, string targetRoleName, bool mustBeNull, object wrapperObject) where TItem : class
159 // Only attempt to load collection if:
161 // 1. Collection is non-null.
162 // 2. ObjectContext.ContextOptions.LazyLoadingEnabled is true
163 // 3. A non-null RelationshipManager can be retrieved (this is asserted).
164 // 4. The EntityCollection is not already loaded.
166 Debug.Assert(wrapperObject == null || wrapperObject is IEntityWrapper, "wrapperObject must be an IEntityWrapper");
167 IEntityWrapper wrapper = (IEntityWrapper)wrapperObject; // We want an exception if the cast fails.
169 if (wrapper != null && wrapper.Context != null)
171 RelationshipManager relationshipManager = wrapper.RelationshipManager;
172 Debug.Assert(relationshipManager != null, "relationshipManager should be non-null");
173 if (relationshipManager != null && (!mustBeNull || propertyValue == null))
175 RelatedEnd relatedEnd = relationshipManager.GetRelatedEndInternal(relationshipName, targetRoleName);
176 relatedEnd.DeferredLoad();
180 return propertyValue != null;