6806c05cd537cd8f979112eb9809bf7567c771dc
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / Internal / LazyLoadBehavior.cs
1 //---------------------------------------------------------------------
2 // <copyright file="LazyLoadedCollectionBehavior.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 using System;
11 using System.Collections.Generic;
12 using System.Diagnostics;
13 using System.Linq;
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;
22
23 namespace System.Data.Objects.Internal
24 {
25     /// <summary>
26     /// Defines and injects behavior into proxy class Type definitions
27     /// to allow navigation properties to lazily load their references or collection elements.
28     /// </summary>
29     internal sealed class LazyLoadBehavior
30     {
31         /// <summary>
32         /// Return an expression tree that represents the actions required to load the related end
33         /// associated with the intercepted proxy member.
34         /// </summary>
35         /// <param name="member">
36         /// EdmMember that specifies the member to be intercepted.
37         /// </param>
38         /// <param name="property">
39         /// PropertyInfo that specifies the CLR property to be intercepted.
40         /// </param>
41         /// <param name="proxyParameter">
42         /// ParameterExpression that represents the proxy object.
43         /// </param>
44         /// <param name="itemParameter">
45         /// ParameterExpression that represents the proxied property value.
46         /// </param>
47         /// <param name="getEntityWrapperDelegate">The Func that retrieves the wrapper from a proxy</param>
48         /// <returns>
49         /// Expression tree that encapsulates lazy loading behavior for the supplied member,
50         /// or null if the expression tree could not be constructed.
51         /// </returns>
52         internal static Func<TProxy, TItem, bool> GetInterceptorDelegate<TProxy, TItem>(EdmMember member, Func<object, object> getEntityWrapperDelegate) 
53             where TProxy : class
54             where TItem : class 
55         {
56             Func<TProxy, TItem, bool> interceptorDelegate = (proxy, item) => true;
57
58             Debug.Assert(member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty, "member should represent a navigation property");
59             if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty)
60             {
61                 NavigationProperty navProperty = (NavigationProperty)member;
62                 RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity;
63
64                 // Given the proxy and item parameters, construct one of the following expressions:
65                 //
66                 // For collections:
67                 //  LazyLoadBehavior.LoadCollection(collection, "relationshipName", "targetRoleName", proxy._entityWrapperField)
68                 //
69                 // For entity references:
70                 //  LazyLoadBehavior.LoadReference(item, "relationshipName", "targetRoleName", proxy._entityWrapperField)
71                 //
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.
74
75                 if (multiplicity == RelationshipMultiplicity.Many)
76                 {
77                     interceptorDelegate = (proxy, item) => LoadProperty<TItem>(item,
78                                                                                navProperty.RelationshipType.Identity,
79                                                                                navProperty.ToEndMember.Identity,
80                                                                                false,
81                                                                                getEntityWrapperDelegate(proxy));
82                 }
83                 else
84                 {
85                     interceptorDelegate = (proxy, item) => LoadProperty<TItem>(item,
86                                                                                navProperty.RelationshipType.Identity,
87                                                                                navProperty.ToEndMember.Identity,
88                                                                                true,
89                                                                                getEntityWrapperDelegate(proxy));
90                 }
91             }
92
93             return interceptorDelegate;
94         }
95
96         /// <summary>
97         /// Determine if the specified member is compatible with lazy loading.
98         /// </summary>
99         /// <param name="ospaceEntityType">
100         /// OSpace EntityType representing a type that may be proxied.
101         /// </param>
102         /// <param name="member">
103         /// Member of the <paramref name="ospaceEntityType" /> to be examined.
104         /// </param>
105         /// <returns>
106         /// True if the member is compatible with lazy loading; otherwise false.
107         /// </returns>
108         /// <remarks>
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&lt;T&gt;.
114         /// </remarks>
115         internal static bool IsLazyLoadCandidate(EntityType ospaceEntityType, EdmMember member)
116         {
117             Debug.Assert(ospaceEntityType.DataSpace == DataSpace.OSpace, "ospaceEntityType.DataSpace must be OSpace");
118
119             bool isCandidate = false;
120
121             if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty)
122             {
123                 NavigationProperty navProperty = (NavigationProperty)member;
124                 RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity;
125
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;
129
130                 if (multiplicity == RelationshipMultiplicity.Many)
131                 {
132                     Type elementType;
133                     isCandidate = EntityUtil.TryGetICollectionElementType(propertyValueType, out elementType);
134                 }
135                 else if (multiplicity == RelationshipMultiplicity.One || multiplicity == RelationshipMultiplicity.ZeroOrOne)
136                 {
137                     // This is an EntityReference property.
138                     isCandidate = true;
139                 }
140             }
141
142             return isCandidate;
143         }
144
145         /// <summary>
146         /// Method called by proxy interceptor delegate to provide lazy loading behavior for navigation properties.
147         /// </summary>
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>
153         /// <returns>
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
156         /// </returns>
157         private static bool LoadProperty<TItem>(TItem propertyValue, string relationshipName, string targetRoleName, bool mustBeNull, object wrapperObject) where TItem : class
158         {
159             // Only attempt to load collection if:
160             //
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.
165
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.
168
169             if (wrapper != null && wrapper.Context != null)
170             {
171                 RelationshipManager relationshipManager = wrapper.RelationshipManager;
172                 Debug.Assert(relationshipManager != null, "relationshipManager should be non-null");
173                 if (relationshipManager != null && (!mustBeNull || propertyValue == null))
174                 {
175                     RelatedEnd relatedEnd = relationshipManager.GetRelatedEndInternal(relationshipName, targetRoleName);
176                     relatedEnd.DeferredLoad();
177                 }
178             }
179
180             return propertyValue != null;
181         }
182     }
183 }