Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / ObjectViewFactory.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ObjectViewFactory.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 using System;
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.ComponentModel;
13 using System.Data.Common;
14 using System.Data.Metadata;
15 using System.Data.Metadata.Edm;
16 using System.Data.Objects.DataClasses;
17 using System.Data.Objects.Internal;
18 using System.Diagnostics;
19 using System.Reflection;
20 using System.Runtime.CompilerServices;
21
22 namespace System.Data.Objects
23 {
24     /// <summary>
25     /// Creates instances of ObjectView that provide a binding list for ObjectQuery results and EntityCollections.
26     /// </summary>
27     /// <remarks>
28     /// The factory methods construct an ObjectView whose generic type parameter (and typed of elements in the binding list)
29     /// is of the same type or a more specific derived type of the generic type of the ObjectQuery or EntityCollection.
30     /// The EDM type of the query results or EntityType or the EntityCollection is examined to determine 
31     /// the appropriate type to be used.
32     /// For example, if you have an ObjectQuery whose generic type is "object", but the EDM result type of the Query maps
33     /// to the CLR type "Customer", then the ObjectView returned will specify a generic type of "Customer", and not "object".
34     /// </remarks>
35     internal static class ObjectViewFactory
36     {
37         // References to commonly-used generic type definitions.
38         private static readonly Type genericObjectViewType = typeof(ObjectView<>);
39
40         private static readonly Type genericObjectViewDataInterfaceType = typeof(IObjectViewData<>);
41         private static readonly Type genericObjectViewQueryResultDataType = typeof(ObjectViewQueryResultData<>);
42         private static readonly Type genericObjectViewEntityCollectionDataType = typeof(ObjectViewEntityCollectionData<,>);
43
44         /// <summary>
45         /// Return a list suitable for data binding using the supplied query results.
46         /// </summary>
47         /// <typeparam name="TElement">
48         /// CLR type of query result elements declared by the caller.
49         /// </typeparam>
50         /// <param name="elementEdmTypeUsage">
51         /// The EDM type of the query results, used as the primary means of determining the 
52         /// CLR type of list returned by this method.
53         /// </param>
54         /// <param name="queryResults">
55         /// IEnumerable used to enumerate query results used to populate binding list.
56         /// Must not be null.
57         /// </param>
58         /// <param name="objectContext">
59         /// <see cref="ObjectContext"/> associated with the query from which results were obtained.
60         /// Must not be null.
61         /// </param>
62         /// <param name="forceReadOnly">
63         /// <b>True</b> to prevent modifications to the binding list built from the query result; otherwise <b>false</b>.
64         /// Note that other conditions may prevent the binding list from being modified, so a value of <b>false</b>
65         /// supplied for this parameter doesn't necessarily mean that the list will be writable.
66         /// </param>
67         /// <param name="singleEntitySet">
68         /// If the query results are composed of entities that only exist in a single <see cref="EntitySet"/>, 
69         /// the value of this parameter is the single EntitySet.
70         /// Otherwise the value of this parameter should be null.
71         /// </param>
72         /// <returns>
73         /// <see cref="IBindingList"/> that is suitable for data binding.
74         /// </returns>
75         [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
76         internal static IBindingList CreateViewForQuery<TElement>(TypeUsage elementEdmTypeUsage, IEnumerable<TElement> queryResults, ObjectContext objectContext, bool forceReadOnly, EntitySet singleEntitySet)
77         {
78             EntityUtil.CheckArgumentNull(queryResults, "queryResults");
79             EntityUtil.CheckArgumentNull(objectContext, "objectContext");
80
81             Type clrElementType = null;
82             TypeUsage ospaceElementTypeUsage = GetOSpaceTypeUsage(elementEdmTypeUsage, objectContext);
83
84             // Map the O-Space EDM type to a CLR type.
85             // If the mapping is unsuccessful, fallback to TElement type.
86             if (ospaceElementTypeUsage == null)
87             {
88                 clrElementType = typeof(TElement);
89             }
90             {
91                 clrElementType = GetClrType<TElement>(ospaceElementTypeUsage.EdmType);
92             }
93
94             IBindingList objectView;
95             object eventDataSource = objectContext.ObjectStateManager;
96
97             // If the clrElementType matches the declared TElement type, optimize the construction of the ObjectView
98             // by avoiding a reflection-based instantiation.
99             if (clrElementType == typeof(TElement))
100             {
101                 ObjectViewQueryResultData<TElement> viewData = new ObjectViewQueryResultData<TElement>((IEnumerable)queryResults, objectContext, forceReadOnly, singleEntitySet);
102
103                 objectView = new ObjectView<TElement>(viewData, eventDataSource);
104             }
105             else if (clrElementType == null)
106             {
107                 ObjectViewQueryResultData<DbDataRecord> viewData = new ObjectViewQueryResultData<DbDataRecord>((IEnumerable)queryResults, objectContext, true, null);
108                 objectView = new DataRecordObjectView(viewData, eventDataSource, (RowType)ospaceElementTypeUsage.EdmType, typeof(TElement));
109             }
110             else
111             {
112                 if (!typeof(TElement).IsAssignableFrom(clrElementType))
113                 {
114                     throw EntityUtil.ValueInvalidCast(clrElementType, typeof(TElement));
115                 }
116
117                 // Use reflection to create an instance of the generic ObjectView and ObjectViewQueryResultData classes, 
118                 // using clrElementType as the value of TElement generic type parameter for both classes.
119
120                 Type objectViewDataType = genericObjectViewQueryResultDataType.MakeGenericType(clrElementType);
121
122                 ConstructorInfo viewDataConstructor = objectViewDataType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,
123                                                                                         null,
124                                                                                         new Type[] { typeof(IEnumerable), typeof(ObjectContext), typeof(bool), typeof(EntitySet) },
125                                                                                         null);
126
127                 Debug.Assert(viewDataConstructor != null, "ObjectViewQueryResultData constructor not found. Please ensure constructor signature is correct.");
128
129                 // Create ObjectViewQueryResultData instance
130                 object viewData = viewDataConstructor.Invoke(new object[] { queryResults, objectContext, forceReadOnly, singleEntitySet });
131
132                 // Create ObjectView instance
133                 objectView = CreateObjectView(clrElementType, objectViewDataType, viewData, eventDataSource);
134             }
135
136             return objectView;
137         }
138
139         /// <summary>
140         /// Return a list suitable for data binding using the supplied EntityCollection
141         /// </summary>
142         /// <typeparam name="TElement">
143         /// CLR type of the elements of the EntityCollection.
144         /// </typeparam>
145         /// <param name="entityType">
146         /// The EntityType of the elements in the collection.
147         /// This should either be the same as the EntityType that corresponds to the CLR TElement type,
148         /// or a EntityType derived from the declared EntityCollection element type.
149         /// </param>
150         /// <param name="entityCollection">
151         /// The EntityCollection from which a binding list is created.
152         /// </param>
153         /// <returns>
154         /// <see cref="IBindingList"/> that is suitable for data binding.
155         /// </returns>
156         [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
157         internal static IBindingList CreateViewForEntityCollection<TElement>(EntityType entityType, EntityCollection<TElement> entityCollection)
158             where TElement : class
159         {
160             Type clrElementType = null;
161             TypeUsage entityTypeUsage = entityType == null ? null : TypeUsage.Create(entityType);
162             TypeUsage ospaceElementTypeUsage = GetOSpaceTypeUsage(entityTypeUsage, entityCollection.ObjectContext);
163
164             // Map the O-Space EDM type to a CLR type.
165             // If the mapping is unsuccessful, fallback to TElement type.
166             if (ospaceElementTypeUsage == null)
167             {
168                 clrElementType = typeof(TElement);
169             }
170             else
171             {
172                 clrElementType = GetClrType<TElement>(ospaceElementTypeUsage.EdmType);
173
174                 // A null clrElementType is returned by GetClrType if the EDM type is a RowType with no specific CLR type mapping.
175                 // This should not happen when working with EntityCollections, but if it does, fallback to TEntityRef type.
176                 Debug.Assert(clrElementType != null, "clrElementType has unexpected value of null.");
177
178                 if (clrElementType == null)
179                 {
180                     clrElementType = typeof(TElement);
181                 }
182             }
183
184             IBindingList objectView;
185
186             // If the clrElementType matches the declared TElement type, optimize the construction of the ObjectView
187             // by avoiding a reflection-based instantiation.
188             if (clrElementType == typeof(TElement))
189             {
190                 ObjectViewEntityCollectionData<TElement, TElement> viewData = new ObjectViewEntityCollectionData<TElement, TElement>(entityCollection);
191                 objectView = new ObjectView<TElement>(viewData, entityCollection);
192             }
193             else
194             {
195                 if (!typeof(TElement).IsAssignableFrom(clrElementType))
196                 {
197                     throw EntityUtil.ValueInvalidCast(clrElementType, typeof(TElement));
198                 }
199
200                 // Use reflection to create an instance of the generic ObjectView and ObjectViewEntityCollectionData classes, 
201                 // using clrElementType as the value of TElement generic type parameter for both classes.
202
203                 Type objectViewDataType = genericObjectViewEntityCollectionDataType.MakeGenericType(clrElementType, typeof(TElement));
204
205                 ConstructorInfo viewDataConstructor = objectViewDataType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,
206                                                                                         null,
207                                                                                         new Type[] { typeof(EntityCollection<TElement>) },
208                                                                                         null);
209
210                 Debug.Assert(viewDataConstructor != null, "ObjectViewEntityCollectionData constructor not found. Please ensure constructor signature is correct.");
211
212                 // Create ObjectViewEntityCollectionData instance
213                 object viewData = viewDataConstructor.Invoke(new object[] { entityCollection });
214
215                 // Create ObjectView instance
216                 objectView = CreateObjectView(clrElementType, objectViewDataType, viewData, entityCollection);
217             }
218
219             return objectView;
220         }
221
222         /// <summary>
223         /// Create an ObjectView using reflection.
224         /// </summary>
225         /// <param name="clrElementType">Type to be used for the ObjectView's generic type parameter.</param>
226         /// <param name="objectViewDataType">The type of class that implements the IObjectViewData to be used by the ObjectView.</param>
227         /// <param name="viewData">The IObjectViewData to be used by the ObjectView to access the binding list.</param>
228         /// <param name="eventDataSource">Event source used by ObjectView for entity and membership changes.</param>
229         /// <returns></returns>
230         [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
231         private static IBindingList CreateObjectView(Type clrElementType, Type objectViewDataType, object viewData, object eventDataSource)
232         {
233             Type objectViewType = genericObjectViewType.MakeGenericType(clrElementType);
234
235             Type[] viewDataInterfaces = objectViewDataType.FindInterfaces((Type type, object unusedFilter) => type.Name == genericObjectViewDataInterfaceType.Name, null);
236             Debug.Assert(viewDataInterfaces.Length == 1, "Could not find IObjectViewData<T> interface definition for ObjectViewQueryResultData<T>.");
237
238             ConstructorInfo viewConstructor = objectViewType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,
239                                                                             null,
240                                                                             new Type[] { viewDataInterfaces[0], typeof(object) },
241                                                                             null);
242
243             Debug.Assert(viewConstructor != null, "ObjectView constructor not found. Please ensure constructor signature is correct.");
244
245             // Create ObjectView instance
246             return (IBindingList)viewConstructor.Invoke(new object[] { viewData, eventDataSource });
247         }
248
249         /// <summary>
250         /// Map the supplied TypeUsage to O-Space.
251         /// </summary>
252         /// <param name="typeUsage">
253         /// The TypeUsage to be mapped to O-Space.  Should either be associated with C-Space or O-Space.
254         /// </param>
255         /// <param name="objectContext">
256         /// ObjectContext used to perform type mapping.
257         /// </param>
258         /// <returns></returns>
259         private static TypeUsage GetOSpaceTypeUsage(TypeUsage typeUsage, ObjectContext objectContext)
260         {
261             TypeUsage ospaceTypeUsage;
262
263             if (typeUsage == null || typeUsage.EdmType == null)
264             {
265                 ospaceTypeUsage = null;
266             }
267             else
268             {
269                 if (typeUsage.EdmType.DataSpace == DataSpace.OSpace)
270                 {
271                     ospaceTypeUsage = typeUsage;
272                 }
273                 else
274                 {
275                     Debug.Assert(typeUsage.EdmType.DataSpace == DataSpace.CSpace, String.Format(System.Globalization.CultureInfo.InvariantCulture, "Expected EdmType.DataSpace to be C-Space, but instead it is {0}.", typeUsage.EdmType.DataSpace.ToString()));
276
277                     // The ObjectContext is needed to map the EDM TypeUsage from C-Space to O-Space.
278                     if (objectContext == null)
279                     {
280                         ospaceTypeUsage = null;
281                     }
282                     else
283                     {
284                         objectContext.EnsureMetadata();
285                         ospaceTypeUsage = objectContext.Perspective.MetadataWorkspace.GetOSpaceTypeUsage(typeUsage);
286                     }
287                 }
288             }
289
290             return ospaceTypeUsage;
291         }
292
293         /// <summary>
294         /// Determine CLR Type to be exposed for data binding using the supplied EDM item type.
295         /// </summary>
296         /// <typeparam name="TElement">
297         /// CLR element type declared by the caller.
298         /// 
299         /// There is no requirement that this method return the same type, or a type compatible with the declared type;
300         /// it is merely a suggestion as to which type might be used.
301         /// </typeparam>
302         /// <param name="ospaceEdmType">
303         /// The EDM O-Space type of the items in a particular query result.
304         /// </param>
305         /// <returns>
306         /// <see cref="Type"/> instance that represents the CLR type that corresponds to the supplied EDM item type;
307         /// or null if the EDM type does not map to a CLR type.
308         /// Null is returned in the case where <paramref name="ospaceEdmType"/> is a <see cref="RowType"/>, 
309         /// and no CLR type mapping is specified in the RowType metadata.
310         /// </returns>
311         private static Type GetClrType<TElement>(EdmType ospaceEdmType)
312         {
313             Type clrType;
314
315             // EDM RowTypes are generally represented by CLR MaterializedDataRecord types
316             // that need special handling to properly expose the properties available for binding (using ICustomTypeDescriptor and ITypedList implementations, for example).
317             //
318             // However, if the RowType has InitializerMetadata with a non-null CLR Type, 
319             // that CLR type should be used to determine the properties available for binding.
320             if (ospaceEdmType.BuiltInTypeKind == BuiltInTypeKind.RowType)
321             {
322                 RowType itemRowType = (RowType)ospaceEdmType;
323
324                 if (itemRowType.InitializerMetadata != null && itemRowType.InitializerMetadata.ClrType != null)
325                 {
326                     clrType = itemRowType.InitializerMetadata.ClrType;
327                 }
328                 else
329                 {
330                     // If the generic parameter TElement is not exactly a data record type or object type,
331                     // use it as the CLR type.
332                     Type elementType = typeof(TElement);
333
334                     if (typeof(IDataRecord).IsAssignableFrom(elementType) || elementType == typeof(object))
335                     {
336                         // No CLR type mapping exists for this RowType.
337                         clrType = null;
338                     }
339                     else
340                     {
341                         clrType = typeof(TElement);
342                     }
343                 }
344             }
345             else
346             {
347                 clrType = ospaceEdmType.ClrType;
348
349                 // If the CLR type cannot be determined from the EDM type,
350                 // fallback to the element type declared by the caller.
351                 if (clrType == null)
352                 {
353                     clrType = typeof(TElement);
354                 }
355             }
356
357             return clrType;
358         }
359     }
360 }