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)] CreateViewForQuery(TypeUsage elementEdmTypeUsage, IEnumerable<TElement> queryResults, ObjectContext objectContext, bool forceReadOnly, EntitySet singleEntitySet)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)] CreateObjectView(Type clrElementType, Type objectViewDataType, object viewData, object eventDataSource)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> GetOSpaceTypeUsage(TypeUsage typeUsage, ObjectContext objectContext)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> GetClrType(EdmType ospaceEdmType)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 } 361