1 //------------------------------------------------------------------------------
2 // <copyright file="ColumnMapFactory.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // @backupOwner Microsoft
7 //------------------------------------------------------------------------------
9 using System.Collections.Generic;
10 using System.Data.Common;
11 using System.Data.Common.Utils;
12 using System.Data.Entity;
13 using System.Data.Mapping;
14 using System.Data.Metadata.Edm;
15 using System.Data.Objects.ELinq;
16 using System.Diagnostics;
18 using System.Linq.Expressions;
19 using System.Reflection;
21 namespace System.Data.Query.InternalTrees
24 /// Factory methods for prescriptive column map patterns (includes default
25 /// column maps for -- soon to be -- public materializer services and function
28 internal static class ColumnMapFactory
31 /// Creates a column map for the given reader and function mapping.
33 internal static CollectionColumnMap CreateFunctionImportStructuralTypeColumnMap(DbDataReader storeDataReader, FunctionImportMappingNonComposable mapping, int resultSetIndex, EntitySet entitySet, StructuralType baseStructuralType)
35 FunctionImportStructuralTypeMappingKB resultMapping = mapping.GetResultMapping(resultSetIndex);
36 Debug.Assert(resultMapping != null);
37 if (resultMapping.NormalizedEntityTypeMappings.Count == 0) // no explicit mapping; use default non-polymorphic reader
39 // if there is no mapping, create default mapping to root entity type or complex type
40 Debug.Assert(!baseStructuralType.Abstract, "mapping loader must verify abstract types have explicit mapping");
42 return CreateColumnMapFromReaderAndType(storeDataReader, baseStructuralType, entitySet, resultMapping.ReturnTypeColumnsRenameMapping);
45 // the section below deals with the polymorphic entity type mapping for return type
46 EntityType baseEntityType = baseStructuralType as EntityType;
47 Debug.Assert(null != baseEntityType, "We should have entity type here");
49 // Generate column maps for all discriminators
50 ScalarColumnMap[] discriminatorColumns = CreateDiscriminatorColumnMaps(storeDataReader, mapping, resultSetIndex);
52 // Generate default maps for all mapped entity types
53 var mappedEntityTypes = new HashSet<EntityType>(resultMapping.MappedEntityTypes);
54 mappedEntityTypes.Add(baseEntityType); // make sure the base type is represented
55 Dictionary<EntityType, TypedColumnMap> typeChoices = new Dictionary<EntityType, TypedColumnMap>(mappedEntityTypes.Count);
56 ColumnMap[] baseTypeColumnMaps = null;
57 foreach (EntityType entityType in mappedEntityTypes)
59 ColumnMap[] propertyColumnMaps = GetColumnMapsForType(storeDataReader, entityType, resultMapping.ReturnTypeColumnsRenameMapping);
60 EntityColumnMap entityColumnMap = CreateEntityTypeElementColumnMap(storeDataReader, entityType, entitySet, propertyColumnMaps, resultMapping.ReturnTypeColumnsRenameMapping);
61 if (!entityType.Abstract)
63 typeChoices.Add(entityType, entityColumnMap);
65 if (entityType == baseStructuralType)
67 baseTypeColumnMaps = propertyColumnMaps;
71 // NOTE: We don't have a null sentinel here, because the stored proc won't
72 // return one anyway; we'll just presume the data's always there.
73 MultipleDiscriminatorPolymorphicColumnMap polymorphicMap = new MultipleDiscriminatorPolymorphicColumnMap(TypeUsage.Create(baseStructuralType), baseStructuralType.Name, baseTypeColumnMaps, discriminatorColumns, typeChoices, (object[] discriminatorValues) => mapping.Discriminate(discriminatorValues, resultSetIndex));
74 CollectionColumnMap collection = new SimpleCollectionColumnMap(baseStructuralType.GetCollectionType().TypeUsage, baseStructuralType.Name, polymorphicMap, null, null);
79 /// Build the collectionColumnMap from a store datareader, a type and an entitySet.
81 /// <param name="storeDataReader"></param>
82 /// <param name="edmType"></param>
83 /// <param name="entitySet"></param>
84 /// <returns></returns>
85 internal static CollectionColumnMap CreateColumnMapFromReaderAndType(DbDataReader storeDataReader, EdmType edmType, EntitySet entitySet, Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping> renameList)
87 Debug.Assert(Helper.IsEntityType(edmType) || null == entitySet, "The specified non-null EntitySet is incompatible with the EDM type specified.");
89 // Next, build the ColumnMap directly from the edmType and entitySet provided.
90 ColumnMap[] propertyColumnMaps = GetColumnMapsForType(storeDataReader, edmType, renameList);
91 ColumnMap elementColumnMap = null;
93 // NOTE: We don't have a null sentinel here, because the stored proc won't
94 // return one anyway; we'll just presume the data's always there.
95 if (Helper.IsRowType(edmType))
97 elementColumnMap = new RecordColumnMap(TypeUsage.Create(edmType), edmType.Name, propertyColumnMaps, null);
99 else if (Helper.IsComplexType(edmType))
101 elementColumnMap = new ComplexTypeColumnMap(TypeUsage.Create(edmType), edmType.Name, propertyColumnMaps, null);
103 else if (Helper.IsScalarType(edmType))
105 if (storeDataReader.FieldCount != 1)
107 throw EntityUtil.CommandExecutionDataReaderFieldCountForScalarType();
109 elementColumnMap = new ScalarColumnMap(TypeUsage.Create(edmType), edmType.Name, 0, 0);
111 else if (Helper.IsEntityType(edmType))
113 elementColumnMap = CreateEntityTypeElementColumnMap(storeDataReader, edmType, entitySet, propertyColumnMaps, null/*renameList*/);
117 Debug.Assert(false, "unexpected edmType?");
119 CollectionColumnMap collection = new SimpleCollectionColumnMap(edmType.GetCollectionType().TypeUsage, edmType.Name, elementColumnMap, null, null);
124 /// Requires: a public type with a public, default constructor. Returns a column map initializing the type
125 /// and all properties of the type with a public setter taking a primitive type and having a corresponding
126 /// column in the reader.
128 internal static CollectionColumnMap CreateColumnMapFromReaderAndClrType(DbDataReader reader, Type type, MetadataWorkspace workspace)
130 Debug.Assert(null != reader);
131 Debug.Assert(null != type);
132 Debug.Assert(null != workspace);
134 // we require a default constructor
135 ConstructorInfo constructor = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
136 null, Type.EmptyTypes, null);
137 if (type.IsAbstract || (null == constructor && !type.IsValueType))
139 throw EntityUtil.InvalidOperation(
140 Strings.ObjectContext_InvalidTypeForStoreQuery(type));
143 // build a LINQ expression used by result assembly to create results
144 var memberInfo = new List<Tuple<MemberAssignment, int, EdmProperty>>();
145 foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
147 // for enums unwrap the type if nullable
148 var propertyUnderlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
149 Type propType = propertyUnderlyingType.IsEnum ? propertyUnderlyingType.GetEnumUnderlyingType() : prop.PropertyType;
153 if (TryGetColumnOrdinalFromReader(reader, prop.Name, out ordinal) &&
154 MetadataHelper.TryDetermineCSpaceModelType(propType, workspace, out modelType) &&
155 (Helper.IsScalarType(modelType)) &&
156 prop.CanWrite && prop.GetIndexParameters().Length == 0 && null != prop.GetSetMethod(/* nonPublic */true))
158 memberInfo.Add(Tuple.Create(
159 Expression.Bind(prop, Expression.Parameter(prop.PropertyType, "placeholder")),
161 new EdmProperty(prop.Name, TypeUsage.Create(modelType))));
164 // initialize members in the order in which they appear in the reader
165 MemberInfo[] members = new MemberInfo[memberInfo.Count];
166 MemberBinding[] memberBindings = new MemberBinding[memberInfo.Count];
167 ColumnMap[] propertyMaps = new ColumnMap[memberInfo.Count];
168 EdmProperty[] modelProperties = new EdmProperty[memberInfo.Count];
170 foreach (var memberGroup in memberInfo.GroupBy(tuple => tuple.Item2).OrderBy(tuple => tuple.Key))
172 // make sure that a single column isn't contributing to multiple properties
173 if (memberGroup.Count() != 1)
175 throw EntityUtil.InvalidOperation(Strings.ObjectContext_TwoPropertiesMappedToSameColumn(
176 reader.GetName(memberGroup.Key),
177 String.Join(", ", memberGroup.Select(tuple => tuple.Item3.Name).ToArray())));
180 var member = memberGroup.Single();
181 MemberAssignment assignment = member.Item1;
182 int ordinal = member.Item2;
183 EdmProperty modelProp = member.Item3;
185 members[i] = assignment.Member;
186 memberBindings[i] = assignment;
187 propertyMaps[i] = new ScalarColumnMap(modelProp.TypeUsage, modelProp.Name, 0, ordinal);
188 modelProperties[i] = modelProp;
191 NewExpression newExpr = null == constructor ? Expression.New(type) : Expression.New(constructor);
192 MemberInitExpression init = Expression.MemberInit(newExpr, memberBindings);
193 InitializerMetadata initMetadata = InitializerMetadata.CreateProjectionInitializer(
194 (EdmItemCollection)workspace.GetItemCollection(DataSpace.CSpace), init, members);
196 // column map (a collection of rows with InitializerMetadata markup)
197 RowType rowType = new RowType(modelProperties, initMetadata);
198 RecordColumnMap rowMap = new RecordColumnMap(TypeUsage.Create(rowType),
199 "DefaultTypeProjection", propertyMaps, null);
200 CollectionColumnMap collectionMap = new SimpleCollectionColumnMap(rowType.GetCollectionType().TypeUsage,
201 rowType.Name, rowMap, null, null);
202 return collectionMap;
206 /// Build the entityColumnMap from a store datareader, a type and an entitySet and
207 /// a list ofproperties.
209 /// <param name="storeDataReader"></param>
210 /// <param name="edmType"></param>
211 /// <param name="entitySet"></param>
212 /// <param name="propertyColumnMaps"></param>
213 /// <returns></returns>
214 private static EntityColumnMap CreateEntityTypeElementColumnMap(
215 DbDataReader storeDataReader, EdmType edmType, EntitySet entitySet,
216 ColumnMap[] propertyColumnMaps, Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping> renameList)
218 EntityType entityType = (EntityType)edmType;
220 // The tricky part here is
221 // that the KeyColumns list must point at the same ColumnMap(s) that
222 // the properties list points to, so we build a quick array of
223 // ColumnMap(s) that are indexed by their ordinal; then we can walk
224 // the list of keyMembers, and find the ordinal in the reader, and
225 // pick the same ColumnMap for it.
227 // Build the ordinal -> ColumnMap index
228 ColumnMap[] ordinalToColumnMap = new ColumnMap[storeDataReader.FieldCount];
230 foreach (ColumnMap propertyColumnMap in propertyColumnMaps)
232 int ordinal = ((ScalarColumnMap)propertyColumnMap).ColumnPos;
233 ordinalToColumnMap[ordinal] = propertyColumnMap;
236 // Now build the list of KeyColumns;
237 IList<EdmMember> keyMembers = entityType.KeyMembers;
238 SimpleColumnMap[] keyColumns = new SimpleColumnMap[keyMembers.Count];
240 int keyMemberIndex = 0;
241 foreach (EdmMember keyMember in keyMembers)
243 int keyOrdinal = GetMemberOrdinalFromReader(storeDataReader, keyMember, edmType, renameList);
245 Debug.Assert(keyOrdinal >= 0, "keyMember for entity is not found by name in the data reader?");
247 ColumnMap keyColumnMap = ordinalToColumnMap[keyOrdinal];
249 Debug.Assert(null != keyColumnMap, "keyMember for entity isn't in properties collection for the entity?");
250 keyColumns[keyMemberIndex] = (SimpleColumnMap)keyColumnMap;
254 SimpleEntityIdentity entityIdentity = new SimpleEntityIdentity(entitySet, keyColumns);
256 EntityColumnMap result = new EntityColumnMap(TypeUsage.Create(edmType), edmType.Name, propertyColumnMaps, entityIdentity);
261 /// For a given edmType, build an array of scalarColumnMaps that map to the columns
262 /// in the store datareader provided. Note that we're hooking things up by name, not
263 /// by ordinal position.
265 /// <param name="storeDataReader"></param>
266 /// <param name="edmType"></param>
267 /// <returns></returns>
268 private static ColumnMap[] GetColumnMapsForType(DbDataReader storeDataReader, EdmType edmType, Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping> renameList)
270 // First get the list of properties; NOTE: we need to hook up the column by name,
272 IBaseList<EdmMember> members = TypeHelpers.GetAllStructuralMembers(edmType);
273 ColumnMap[] propertyColumnMaps = new ColumnMap[members.Count];
276 foreach (EdmMember member in members)
278 if (!Helper.IsScalarType(member.TypeUsage.EdmType))
280 throw EntityUtil.InvalidOperation(Strings.ADP_InvalidDataReaderUnableToMaterializeNonScalarType(member.Name, member.TypeUsage.EdmType.FullName));
283 int ordinal = GetMemberOrdinalFromReader(storeDataReader, member, edmType, renameList);
285 propertyColumnMaps[index] = new ScalarColumnMap(member.TypeUsage, member.Name, 0, ordinal);
288 return propertyColumnMaps;
291 private static ScalarColumnMap[] CreateDiscriminatorColumnMaps(DbDataReader storeDataReader, FunctionImportMappingNonComposable mapping, int resultIndex)
293 // choose an arbitrary type for discriminator columns -- the type is not
294 // actually statically known
295 EdmType discriminatorType =
296 MetadataItem.EdmProviderManifest.GetPrimitiveType(PrimitiveTypeKind.String);
297 TypeUsage discriminatorTypeUsage =
298 TypeUsage.Create(discriminatorType);
300 IList<string> discriminatorColumnNames = mapping.GetDiscriminatorColumns(resultIndex);
301 ScalarColumnMap[] discriminatorColumns = new ScalarColumnMap[discriminatorColumnNames.Count];
302 for (int i = 0; i < discriminatorColumns.Length; i++)
304 string columnName = discriminatorColumnNames[i];
305 ScalarColumnMap columnMap = new ScalarColumnMap(discriminatorTypeUsage, columnName, 0,
306 GetDiscriminatorOrdinalFromReader(storeDataReader, columnName, mapping.FunctionImport));
307 discriminatorColumns[i] = columnMap;
309 return discriminatorColumns;
313 /// Given a store datareader and a member of an edmType, find the column ordinal
314 /// in the datareader with the name of the member.
316 /// <param name="storeDataReader"></param>
317 /// <param name="member"></param>
318 /// <returns></returns>
319 private static int GetMemberOrdinalFromReader(DbDataReader storeDataReader, EdmMember member, EdmType currentType, Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping> renameList)
322 string memberName = GetRenameForMember(member, currentType, renameList);
324 if (!TryGetColumnOrdinalFromReader(storeDataReader, memberName, out result))
326 throw EntityUtil.CommandExecutionDataReaderMissingColumnForType(member, currentType);
331 private static string GetRenameForMember(EdmMember member, EdmType currentType, Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping> renameList)
334 // or no rename mapping at all,
335 // or partial rename and the member is not specified by the renaming
336 // then we return the original member.Name
337 // otherwise we return the mapped one
338 return renameList == null || renameList.Count == 0 || !renameList.Any(m => m.Key == member.Name) ? member.Name : renameList[member.Name].GetRename(currentType);
342 /// Given a store datareader, a column name, find the column ordinal
343 /// in the datareader with the name of the column.
345 /// We only have the functionImport provided to include it in the exception
348 /// <param name="storeDataReader"></param>
349 /// <param name="columnName"></param>
350 /// <param name="functionImport"></param>
351 /// <returns></returns>
352 private static int GetDiscriminatorOrdinalFromReader(DbDataReader storeDataReader, string columnName, EdmFunction functionImport)
355 if (!TryGetColumnOrdinalFromReader(storeDataReader, columnName, out result))
357 throw EntityUtil.CommandExecutionDataReaderMissinDiscriminatorColumn(columnName, functionImport);
363 /// Given a store datareader and a column name, try to find the column ordinal
364 /// in the datareader with the name of the column.
366 /// <param name="storeDataReader"></param>
367 /// <param name="columnName"></param>
368 /// <param name="ordinal"></param>
369 /// <returns>true if found, false otherwise.</returns>
370 private static bool TryGetColumnOrdinalFromReader(DbDataReader storeDataReader, string columnName, out int ordinal)
372 if (0 == storeDataReader.FieldCount)
374 // If there are no fields, there can't be a match (this check avoids
375 // an InvalidOperationException on the call to GetOrdinal)
376 ordinal = default(int);
380 // Wrap ordinal lookup for the member so that we can throw a nice exception.
383 ordinal = storeDataReader.GetOrdinal(columnName);
386 catch (IndexOutOfRangeException)
388 // No column matching the column name found
389 ordinal = default(int);