1 //--------------------------------------------------------------------- 2 // <copyright file="CellCreator.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.Data.Common.Utils; 11 using System.Data.Mapping.ViewGeneration.Structures; 12 using System.Collections.Generic; 13 using System.Data.Mapping.ViewGeneration.Utils; 14 using System.Diagnostics; 15 using System.Data.Metadata.Edm; 16 using System.Linq; 17 18 namespace System.Data.Mapping.ViewGeneration 19 { 20 /// <summary> 21 /// A class that handles creation of cells from the meta data information. 22 /// </summary> 23 internal class CellCreator : InternalBase 24 { 25 26 #region Constructors 27 // effects: Creates a cell creator object for an entity container's 28 // mappings (specified in "maps") CellCreator(StorageEntityContainerMapping containerMapping)29 internal CellCreator(StorageEntityContainerMapping containerMapping) 30 { 31 m_containerMapping = containerMapping; 32 m_identifiers = new CqlIdentifiers(); 33 } 34 #endregion 35 36 #region Fields 37 // The mappings from the metadata for different containers 38 private StorageEntityContainerMapping m_containerMapping; 39 private int m_currentCellNumber; 40 private CqlIdentifiers m_identifiers; 41 // Keep track of all the identifiers to prevent clashes with _from0, 42 // _from1, T, T1, etc 43 // Keep track of names of 44 // * Entity Containers 45 // * Extent names 46 // * Entity Types 47 // * Complex Types 48 // * Properties 49 // * Roles 50 #endregion 51 52 #region Properties 53 // effects: Returns the set of identifiers used in this 54 internal CqlIdentifiers Identifiers 55 { 56 get { return m_identifiers; } 57 } 58 #endregion 59 60 #region External methods 61 // effects: Generates the cells for all the entity containers 62 // specified in this. The generated cells are geared for query view generation GenerateCells(ConfigViewGenerator config)63 internal List<Cell> GenerateCells(ConfigViewGenerator config) 64 { 65 List<Cell> cells = new List<Cell>(); 66 67 // Get the cells from the entity container metadata 68 ExtractCells(cells); 69 70 ExpandCells(cells); 71 72 // Get the identifiers from the cells 73 m_identifiers.AddIdentifier(m_containerMapping.EdmEntityContainer.Name); 74 m_identifiers.AddIdentifier(m_containerMapping.StorageEntityContainer.Name); 75 foreach (Cell cell in cells) 76 { 77 cell.GetIdentifiers(m_identifiers); 78 } 79 80 return cells; 81 } 82 #endregion 83 84 #region Private Methods 85 /// <summary> 86 /// Boolean members have a closed domain and are enumerated when domains are established i.e. (T, F) instead of (notNull). 87 /// Query Rewriting is exercised over every domain of the condition member. If the member contains not_null condition 88 /// for example, it cannot generate a view for partitions (member=T), (Member=F). For this reason we need to expand the cells 89 /// in a predefined situation (below) to include sub-fragments mapping individual elements of the closed domain. 90 /// Enums (a planned feature) need to be handled in a similar fashion. 91 /// 92 /// Find booleans that are projected with a not_null condition 93 /// Expand ALL cells where they are projected. Why? See Unit Test case NullabilityConditionOnBoolean5.es 94 /// Validation will fail because it will not be able to validate rewritings for partitions on the 'other' cells. 95 /// </summary> ExpandCells(List<Cell> cells)96 private void ExpandCells(List<Cell> cells) 97 { 98 var sSideMembersToBeExpanded = new Set<MemberPath>(); 99 100 foreach (Cell cell in cells) 101 { 102 //Find Projected members that are Boolean AND are mentioned in the Where clause with not_null condition 103 foreach (var memberToExpand in cell.SQuery.GetProjectedMembers() 104 .Where(member => IsBooleanMember(member)) 105 .Where(boolMember => cell.SQuery.GetConjunctsFromWhereClause() 106 .Where(restriction => restriction.Domain.Values.Contains(Constant.NotNull)) 107 .Select(restriction => restriction.RestrictedMemberSlot.MemberPath).Contains(boolMember))) 108 { 109 sSideMembersToBeExpanded.Add(memberToExpand); 110 } 111 } 112 113 //Foreach s-side members, find all c-side members it is mapped to 114 // We need these because we need to expand all cells where the boolean candidate is projected or mapped member is projected, e.g: 115 // (1) C[id, cdisc] WHERE d=true <=> T1[id, sdisc] WHERE sdisc=NOTNULL 116 // (2) C[id, cdisc] WHERE d=false <=> T2[id, sdisc] 117 // Here we need to know that because of T1.sdisc, we need to expand T2.sdisc. 118 // This is done by tracking cdisc, and then seeing in cell 2 that it is mapped to T2.sdisc 119 120 var cSideMembersForSSideExpansionCandidates = new Dictionary<MemberPath, Set<MemberPath>>(); 121 foreach (Cell cell in cells) 122 { 123 foreach (var sSideMemberToExpand in sSideMembersToBeExpanded) 124 { 125 var cSideMembers = cell.SQuery.GetProjectedPositions(sSideMemberToExpand).Select(pos => ((MemberProjectedSlot)cell.CQuery.ProjectedSlotAt(pos)).MemberPath); 126 127 Set<MemberPath> cSidePaths = null; 128 if (!cSideMembersForSSideExpansionCandidates.TryGetValue(sSideMemberToExpand, out cSidePaths)) 129 { 130 cSidePaths = new Set<MemberPath>(); 131 cSideMembersForSSideExpansionCandidates[sSideMemberToExpand] = cSidePaths; 132 } 133 134 cSidePaths.AddRange(cSideMembers); 135 } 136 } 137 138 // Expand cells that project members collected earlier with T/F conditiions 139 foreach (Cell cell in cells.ToArray()) 140 { 141 //Each member gets its own expansion. Including multiple condition candidates in one SQuery 142 // "... <=> T[..] WHERE a=notnull AND b=notnull" means a and b get their own independent expansions 143 // Note: this is not a cross-product 144 foreach (var memberToExpand in sSideMembersToBeExpanded) 145 { 146 var mappedCSideMembers = cSideMembersForSSideExpansionCandidates[memberToExpand]; 147 148 //Check if member is projected in this cell. 149 if (cell.SQuery.GetProjectedMembers().Contains(memberToExpand)) 150 { 151 // Creationg additional cel can fail when the condition to be appended contradicts existing condition in the CellQuery 152 // We don't add contradictions because they seem to cause unrelated problems in subsequent validation routines 153 Cell resultCell = null; 154 if (TryCreateAdditionalCellWithCondition(cell, memberToExpand, true /*condition value*/, ViewTarget.UpdateView /*s-side member*/, out resultCell)) 155 { 156 cells.Add(resultCell); 157 } 158 if (TryCreateAdditionalCellWithCondition(cell, memberToExpand, false /*condition value*/, ViewTarget.UpdateView /*s-side member*/, out resultCell)) 159 { 160 cells.Add(resultCell); 161 } 162 } 163 else 164 { //If the s-side member is not projected, see if the mapped C-side member(s) is projected 165 foreach (var cMemberToExpand in cell.CQuery.GetProjectedMembers().Intersect(mappedCSideMembers)) 166 { 167 Cell resultCell = null; 168 if (TryCreateAdditionalCellWithCondition(cell, cMemberToExpand, true /*condition value*/, ViewTarget.QueryView /*c-side member*/, out resultCell)) 169 { 170 cells.Add(resultCell); 171 } 172 173 if (TryCreateAdditionalCellWithCondition(cell, cMemberToExpand, false /*condition value*/, ViewTarget.QueryView /*c-side member*/, out resultCell)) 174 { 175 cells.Add(resultCell); 176 } 177 } 178 } 179 } 180 } 181 182 } 183 184 /// <summary> 185 /// Given a cell, a member and a boolean condition on that member, creates additional cell 186 /// which with the specified restriction on the member in addition to original condition. 187 /// e.i conjunction of original condition AND member in newCondition 188 /// 189 /// Creation fails when the original condition contradicts new boolean condition 190 /// 191 /// ViewTarget tells whether MemberPath is in Cquery or SQuery 192 /// </summary> TryCreateAdditionalCellWithCondition(Cell originalCell, MemberPath memberToExpand, bool conditionValue, ViewTarget viewTarget, out Cell result)193 private bool TryCreateAdditionalCellWithCondition(Cell originalCell, MemberPath memberToExpand, bool conditionValue, ViewTarget viewTarget, out Cell result) 194 { 195 Debug.Assert(originalCell != null); 196 Debug.Assert(memberToExpand != null); 197 result = null; 198 199 //Create required structures 200 MemberPath leftExtent = originalCell.GetLeftQuery(viewTarget).SourceExtentMemberPath; 201 MemberPath rightExtent = originalCell.GetRightQuery(viewTarget).SourceExtentMemberPath; 202 203 //Now for the given left-side projected member, find corresponding right-side member that it is mapped to 204 int indexOfBooLMemberInProjection = originalCell.GetLeftQuery(viewTarget).GetProjectedMembers().TakeWhile(path => !path.Equals(memberToExpand)).Count(); 205 MemberProjectedSlot rightConditionMemberSlot = ((MemberProjectedSlot)originalCell.GetRightQuery(viewTarget).ProjectedSlotAt(indexOfBooLMemberInProjection)); 206 MemberPath rightSidePath = rightConditionMemberSlot.MemberPath; 207 208 List<ProjectedSlot> leftSlots = new List<ProjectedSlot>(); 209 List<ProjectedSlot> rightSlots = new List<ProjectedSlot>(); 210 211 //Check for impossible conditions (otehrwise we get inaccurate pre-validation errors) 212 ScalarConstant negatedCondition = new ScalarConstant(!conditionValue); 213 214 if (originalCell.GetLeftQuery(viewTarget).Conditions 215 .Where(restriction => restriction.RestrictedMemberSlot.MemberPath.Equals(memberToExpand)) 216 .Where(restriction => restriction.Domain.Values.Contains(negatedCondition)).Any() 217 || originalCell.GetRightQuery(viewTarget).Conditions 218 .Where(restriction => restriction.RestrictedMemberSlot.MemberPath.Equals(rightSidePath)) 219 .Where(restriction => restriction.Domain.Values.Contains(negatedCondition)).Any()) 220 { 221 return false; 222 } 223 //End check 224 225 //Create Projected Slots 226 // Map all slots in original cell (not just keys) because some may be required (non nullable and no default) 227 // and others may have not_null condition so MUST be projected. Rely on the user doing the right thing, otherwise 228 // they will get the error message anyway 229 for (int i = 0; i < originalCell.GetLeftQuery(viewTarget).NumProjectedSlots; i++) 230 { 231 leftSlots.Add(originalCell.GetLeftQuery(viewTarget).ProjectedSlotAt(i)); 232 } 233 234 for (int i = 0; i < originalCell.GetRightQuery(viewTarget).NumProjectedSlots; i++) 235 { 236 rightSlots.Add(originalCell.GetRightQuery(viewTarget).ProjectedSlotAt(i)); 237 } 238 239 //Create condition boolena expressions 240 BoolExpression leftQueryWhereClause = BoolExpression.CreateLiteral(new ScalarRestriction(memberToExpand, new ScalarConstant(conditionValue)), null); 241 leftQueryWhereClause = BoolExpression.CreateAnd(originalCell.GetLeftQuery(viewTarget).WhereClause, leftQueryWhereClause); 242 243 BoolExpression rightQueryWhereClause = BoolExpression.CreateLiteral(new ScalarRestriction(rightSidePath, new ScalarConstant(conditionValue)), null); 244 rightQueryWhereClause = BoolExpression.CreateAnd(originalCell.GetRightQuery(viewTarget).WhereClause, rightQueryWhereClause); 245 246 //Create additional Cells 247 CellQuery rightQuery = new CellQuery(rightSlots, rightQueryWhereClause, rightExtent, originalCell.GetRightQuery(viewTarget).SelectDistinctFlag); 248 CellQuery leftQuery = new CellQuery(leftSlots, leftQueryWhereClause, leftExtent, originalCell.GetLeftQuery(viewTarget).SelectDistinctFlag); 249 250 Cell newCell; 251 if (viewTarget == ViewTarget.UpdateView) 252 { 253 newCell = Cell.CreateCS(rightQuery, leftQuery, originalCell.CellLabel, m_currentCellNumber); 254 } 255 else 256 { 257 newCell = Cell.CreateCS(leftQuery, rightQuery, originalCell.CellLabel, m_currentCellNumber); 258 } 259 260 m_currentCellNumber++; 261 result = newCell; 262 return true; 263 } 264 265 // effects: Given the metadata information for a container in 266 // containerMap, generate the cells for it and modify cells to 267 // contain the newly-generated cells ExtractCells(List<Cell> cells)268 private void ExtractCells(List<Cell> cells) 269 { 270 // extract entity mappings, i.e., for CPerson1, COrder1, etc 271 foreach (StorageSetMapping extentMap in m_containerMapping.AllSetMaps) 272 { 273 274 // Get each type map in an entity set mapping, i.e., for 275 // CPerson, CCustomer, etc in CPerson1 276 foreach (StorageTypeMapping typeMap in extentMap.TypeMappings) 277 { 278 279 StorageEntityTypeMapping entityTypeMap = typeMap as StorageEntityTypeMapping; 280 Debug.Assert(entityTypeMap != null || 281 typeMap is StorageAssociationTypeMapping, "Invalid typemap"); 282 283 // A set for all the types in this type mapping 284 Set<EdmType> allTypes = new Set<EdmType>(); 285 286 if (entityTypeMap != null) 287 { 288 // Gather a set of all explicit types for an entity 289 // type mapping in allTypes. Note that we do not have 290 // subtyping in association sets 291 allTypes.AddRange(entityTypeMap.Types); 292 foreach (EdmType type in entityTypeMap.IsOfTypes) 293 { 294 IEnumerable<EdmType> typeAndSubTypes = MetadataHelper.GetTypeAndSubtypesOf(type, m_containerMapping.StorageMappingItemCollection.EdmItemCollection, false /*includeAbstractTypes*/); 295 allTypes.AddRange(typeAndSubTypes); 296 } 297 } 298 299 EntitySetBase extent = extentMap.Set; 300 Debug.Assert(extent != null, "Extent map for a null extent or type of extentMap.Exent " + 301 "is not Extent"); 302 303 // For each table mapping for the type mapping, we create cells 304 foreach (StorageMappingFragment fragmentMap in typeMap.MappingFragments) 305 { 306 ExtractCellsFromTableFragment(extent, fragmentMap, allTypes, cells); 307 } 308 } 309 } 310 } 311 312 // effects: Given an extent's ("extent") table fragment that is 313 // contained inside typeMap, determine the cells that need to be 314 // created and add them to cells 315 // allTypes corresponds to all the different types that the type map 316 // represents -- this parameter has something useful only if extent 317 // is an entity set ExtractCellsFromTableFragment(EntitySetBase extent, StorageMappingFragment fragmentMap, Set<EdmType> allTypes, List<Cell> cells)318 private void ExtractCellsFromTableFragment(EntitySetBase extent, StorageMappingFragment fragmentMap, 319 Set<EdmType> allTypes, List<Cell> cells) 320 { 321 322 // create C-query components 323 MemberPath cRootExtent = new MemberPath(extent); 324 BoolExpression cQueryWhereClause = BoolExpression.True; 325 List<ProjectedSlot> cSlots = new List<ProjectedSlot>(); 326 327 if (allTypes.Count > 0) 328 { 329 // Create a type condition for the extent, i.e., "extent in allTypes" 330 cQueryWhereClause = BoolExpression.CreateLiteral(new TypeRestriction(cRootExtent, allTypes), null); 331 } 332 333 // create S-query components 334 MemberPath sRootExtent = new MemberPath(fragmentMap.TableSet); 335 BoolExpression sQueryWhereClause = BoolExpression.True; 336 List<ProjectedSlot> sSlots = new List<ProjectedSlot>(); 337 338 // Association or entity set 339 // Add the properties and the key properties to a list and 340 // then process them in ExtractProperties 341 ExtractProperties(fragmentMap.AllProperties, cRootExtent, cSlots, ref cQueryWhereClause, sRootExtent, sSlots, ref sQueryWhereClause); 342 343 // limitation of MSL API: cannot assign constant values to table columns 344 CellQuery cQuery = new CellQuery(cSlots, cQueryWhereClause, cRootExtent, CellQuery.SelectDistinct.No /*no distinct flag*/); 345 CellQuery sQuery = new CellQuery(sSlots, sQueryWhereClause, sRootExtent, 346 fragmentMap.IsSQueryDistinct ? CellQuery.SelectDistinct.Yes : CellQuery.SelectDistinct.No); 347 348 StorageMappingFragment fragmentInfo = fragmentMap as StorageMappingFragment; 349 Debug.Assert((fragmentInfo != null), "CSMappingFragment should support Line Info"); 350 CellLabel label = new CellLabel(fragmentInfo); 351 Cell cell = Cell.CreateCS(cQuery, sQuery, label, m_currentCellNumber); 352 m_currentCellNumber++; 353 cells.Add(cell); 354 } 355 356 // requires: "properties" corresponds to all the properties that are 357 // inside cNode.Value, e.g., cNode corresponds to an extent Person, 358 // properties contains all the properties inside Person (recursively) 359 // effects: Given C-side and S-side Cell Query for a cell, generates 360 // the projected slots on both sides corresponding to 361 // properties. Also updates the C-side whereclause corresponding to 362 // discriminator properties on the C-side, e.g, isHighPriority ExtractProperties(IEnumerable<StoragePropertyMapping> properties, MemberPath cNode, List<ProjectedSlot> cSlots, ref BoolExpression cQueryWhereClause, MemberPath sRootExtent, List<ProjectedSlot> sSlots, ref BoolExpression sQueryWhereClause)363 private void ExtractProperties(IEnumerable<StoragePropertyMapping> properties, 364 MemberPath cNode, List<ProjectedSlot> cSlots, 365 ref BoolExpression cQueryWhereClause, 366 MemberPath sRootExtent, 367 List<ProjectedSlot> sSlots, 368 ref BoolExpression sQueryWhereClause) 369 { 370 // For each property mapping, we add an entry to the C and S cell queries 371 foreach (StoragePropertyMapping propMap in properties) 372 { 373 StorageScalarPropertyMapping scalarPropMap = propMap as StorageScalarPropertyMapping; 374 StorageComplexPropertyMapping complexPropMap = propMap as StorageComplexPropertyMapping; 375 StorageEndPropertyMapping associationEndPropertypMap = propMap as StorageEndPropertyMapping; 376 StorageConditionPropertyMapping conditionMap = propMap as StorageConditionPropertyMapping; 377 378 Debug.Assert(scalarPropMap != null || 379 complexPropMap != null || 380 associationEndPropertypMap != null || 381 conditionMap != null, "Unimplemented property mapping"); 382 383 if (scalarPropMap != null) 384 { 385 Debug.Assert(scalarPropMap.ColumnProperty != null, "ColumnMember for a Scalar Property can not be null"); 386 // Add an attribute node to node 387 388 MemberPath cAttributeNode = new MemberPath(cNode, scalarPropMap.EdmProperty); 389 // Add a column (attribute) node the sQuery 390 // unlike the C side, there is no nesting. Hence we 391 // did not need an internal node 392 MemberPath sAttributeNode = new MemberPath(sRootExtent, scalarPropMap.ColumnProperty); 393 cSlots.Add(new MemberProjectedSlot(cAttributeNode)); 394 sSlots.Add(new MemberProjectedSlot(sAttributeNode)); 395 } 396 397 // Note: S-side constants are not allowed since they can cause 398 // problems -- for example, if such a cell says 5 for the 399 // third field, we cannot guarantee the fact that an 400 // application may not set that field to 7 in the C-space 401 402 // Check if the property mapping is for a complex types 403 if (complexPropMap != null) 404 { 405 foreach (StorageComplexTypeMapping complexTypeMap in complexPropMap.TypeMappings) 406 { 407 // Create a node for the complex type property and call recursively 408 MemberPath complexMemberNode = new MemberPath(cNode, complexPropMap.EdmProperty); 409 //Get the list of types that this type map represents 410 Set<EdmType> allTypes = new Set<EdmType>(); 411 // Gather a set of all explicit types for an entity 412 // type mapping in allTypes. 413 IEnumerable<EdmType> exactTypes = Helpers.AsSuperTypeList<ComplexType, EdmType>(complexTypeMap.Types); 414 allTypes.AddRange(exactTypes); 415 foreach (EdmType type in complexTypeMap.IsOfTypes) 416 { 417 allTypes.AddRange(MetadataHelper.GetTypeAndSubtypesOf(type, m_containerMapping.StorageMappingItemCollection.EdmItemCollection, false /*includeAbstractTypes*/)); 418 } 419 BoolExpression complexInTypes = BoolExpression.CreateLiteral(new TypeRestriction(complexMemberNode, allTypes), null); 420 cQueryWhereClause = BoolExpression.CreateAnd(cQueryWhereClause, complexInTypes); 421 // Now extract the properties of the complex type 422 // (which could have other complex types) 423 ExtractProperties(complexTypeMap.AllProperties, complexMemberNode, cSlots, 424 ref cQueryWhereClause, sRootExtent, sSlots, ref sQueryWhereClause); 425 } 426 } 427 428 // Check if the property mapping is for an associaion 429 if (associationEndPropertypMap != null) 430 { 431 // create join tree node representing this relation end 432 MemberPath associationEndNode = new MemberPath(cNode, associationEndPropertypMap.EndMember); 433 // call recursively 434 ExtractProperties(associationEndPropertypMap.Properties, associationEndNode, cSlots, 435 ref cQueryWhereClause, sRootExtent, sSlots, ref sQueryWhereClause); 436 } 437 438 //Check if the this is a condition and add it to the Where clause 439 if (conditionMap != null) 440 { 441 if (conditionMap.ColumnProperty != null) 442 { 443 //Produce a Condition Expression for the Condition Map. 444 BoolExpression conditionExpression = GetConditionExpression(sRootExtent, conditionMap); 445 //Add the condition expression to the exisiting S side Where clause using an "And" 446 sQueryWhereClause = BoolExpression.CreateAnd(sQueryWhereClause, conditionExpression); 447 } 448 else 449 { 450 Debug.Assert(conditionMap.EdmProperty != null); 451 //Produce a Condition Expression for the Condition Map. 452 BoolExpression conditionExpression = GetConditionExpression(cNode, conditionMap); 453 //Add the condition expression to the exisiting C side Where clause using an "And" 454 cQueryWhereClause = BoolExpression.CreateAnd(cQueryWhereClause, conditionExpression); 455 } 456 457 } 458 } 459 } 460 461 /// <summary> 462 /// Takes in a JoinTreeNode and a Contition Property Map and creates an BoolExpression 463 /// for the Condition Map. 464 /// </summary> 465 /// <param name="joinTreeNode"></param> 466 /// <param name="conditionMap"></param> 467 /// <returns></returns> GetConditionExpression(MemberPath member, StorageConditionPropertyMapping conditionMap)468 private static BoolExpression GetConditionExpression(MemberPath member, StorageConditionPropertyMapping conditionMap) 469 { 470 //Get the member for which the condition is being specified 471 EdmMember conditionMember = (conditionMap.ColumnProperty != null) ? conditionMap.ColumnProperty : conditionMap.EdmProperty; 472 473 MemberPath conditionMemberNode = new MemberPath(member, conditionMember); 474 //Check if this is a IsNull condition 475 MemberRestriction conditionExpression = null; 476 if (conditionMap.IsNull.HasValue) 477 { 478 // for conditions on scalars, create NodeValue nodes, otherwise NodeType 479 Constant conditionConstant = (true == conditionMap.IsNull.Value) ? Constant.Null : Constant.NotNull; 480 if (true == MetadataHelper.IsNonRefSimpleMember(conditionMember)) 481 { 482 conditionExpression = new ScalarRestriction(conditionMemberNode, conditionConstant); 483 } 484 else 485 { 486 conditionExpression = new TypeRestriction(conditionMemberNode, conditionConstant); 487 } 488 } 489 else 490 { 491 conditionExpression = new ScalarRestriction(conditionMemberNode, new ScalarConstant(conditionMap.Value)); 492 } 493 494 Debug.Assert(conditionExpression != null); 495 496 return BoolExpression.CreateLiteral(conditionExpression, null); 497 } 498 IsBooleanMember(MemberPath path)499 private static bool IsBooleanMember(MemberPath path) 500 { 501 PrimitiveType primitive = path.EdmType as PrimitiveType; 502 return (primitive != null && primitive.PrimitiveTypeKind == PrimitiveTypeKind.Boolean); 503 } 504 #endregion 505 506 #region String methods ToCompactString(System.Text.StringBuilder builder)507 internal override void ToCompactString(System.Text.StringBuilder builder) 508 { 509 builder.Append("CellCreator"); // No state to really show i.e., m_maps 510 } 511 512 #endregion 513 514 } 515 } 516