File: LazyLoadBehavior.cs

package info (click to toggle)
mono 6.12.0.199%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 1,296,836 kB
  • sloc: cs: 11,181,803; xml: 2,850,076; ansic: 699,709; cpp: 123,344; perl: 59,361; javascript: 30,841; asm: 21,853; makefile: 20,405; sh: 15,009; python: 4,839; pascal: 925; sql: 859; sed: 16; php: 1
file content (183 lines) | stat: -rw-r--r-- 9,288 bytes parent folder | download | duplicates (8)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//---------------------------------------------------------------------
// <copyright file="LazyLoadedCollectionBehavior.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------

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
{
    /// <summary>
    /// Defines and injects behavior into proxy class Type definitions
    /// to allow navigation properties to lazily load their references or collection elements.
    /// </summary>
    internal sealed class LazyLoadBehavior
    {
        /// <summary>
        /// Return an expression tree that represents the actions required to load the related end
        /// associated with the intercepted proxy member.
        /// </summary>
        /// <param name="member">
        /// EdmMember that specifies the member to be intercepted.
        /// </param>
        /// <param name="property">
        /// PropertyInfo that specifies the CLR property to be intercepted.
        /// </param>
        /// <param name="proxyParameter">
        /// ParameterExpression that represents the proxy object.
        /// </param>
        /// <param name="itemParameter">
        /// ParameterExpression that represents the proxied property value.
        /// </param>
        /// <param name="getEntityWrapperDelegate">The Func that retrieves the wrapper from a proxy</param>
        /// <returns>
        /// Expression tree that encapsulates lazy loading behavior for the supplied member,
        /// or null if the expression tree could not be constructed.
        /// </returns>
        internal static Func<TProxy, TItem, bool> GetInterceptorDelegate<TProxy, TItem>(EdmMember member, Func<object, object> getEntityWrapperDelegate) 
            where TProxy : class
            where TItem : class 
        {
            Func<TProxy, TItem, bool> 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<TItem>(item,
                                                                               navProperty.RelationshipType.Identity,
                                                                               navProperty.ToEndMember.Identity,
                                                                               false,
                                                                               getEntityWrapperDelegate(proxy));
                }
                else
                {
                    interceptorDelegate = (proxy, item) => LoadProperty<TItem>(item,
                                                                               navProperty.RelationshipType.Identity,
                                                                               navProperty.ToEndMember.Identity,
                                                                               true,
                                                                               getEntityWrapperDelegate(proxy));
                }
            }

            return interceptorDelegate;
        }

        /// <summary>
        /// Determine if the specified member is compatible with lazy loading.
        /// </summary>
        /// <param name="ospaceEntityType">
        /// OSpace EntityType representing a type that may be proxied.
        /// </param>
        /// <param name="member">
        /// Member of the <paramref name="ospaceEntityType" /> to be examined.
        /// </param>
        /// <returns>
        /// True if the member is compatible with lazy loading; otherwise false.
        /// </returns>
        /// <remarks>
        /// 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&lt;T&gt;.
        /// </remarks>
        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;
        }

        /// <summary>
        /// Method called by proxy interceptor delegate to provide lazy loading behavior for navigation properties.
        /// </summary>
        /// <typeparam name="TItem">property type</typeparam>
        /// <param name="propertyValue">The property value whose associated relationship is to be loaded.</param>
        /// <param name="relationshipName">String name of the relationship.</param>
        /// <param name="targetRoleName">String name of the related end to be loaded for the relationship specified by <paramref name="relationshipName"/>.</param>
        /// <param name="wrapperObject">Entity wrapper object used to retrieve RelationshipManager for the proxied entity.</param>
        /// <returns>
        /// True if the value instance was mutated and can be returned
        /// False if the class should refetch the value because the instance has changed
        /// </returns>
        private static bool LoadProperty<TItem>(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;
        }
    }
}