1 //------------------------------------------------------------------------------
2 // <copyright file="EntityDataSourceDataSelection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
9 // Manages the properties that can be set on the second page of the wizard
10 //------------------------------------------------------------------------------
12 namespace System.Web.UI.Design.WebControls
14 using System.Collections.Generic;
15 using System.Data.Metadata.Edm;
16 using System.Diagnostics;
17 using System.Globalization;
20 internal class EntityDataSourceDataSelection
22 #region Private static fields
23 // iterator prefix used in building and parsing Select property value
24 private static readonly string s_itKeyword = "it.";
25 // Placeholder item to indicate (None) on the EntityTypeFilter ComboBox
26 private static readonly EntityDataSourceEntityTypeFilterItem s_entityTypeFilterNoneItem =
27 new EntityDataSourceEntityTypeFilterItem(Strings.Wizard_DataSelectionPanel_NoEntityTypeFilter);
30 #region Private readonly fields
31 private readonly EntityDataSourceDataSelectionPanel _panel;
32 private readonly EntityDataSourceDesignerHelper _helper;
35 #region Private fields for temporary storage of property values
36 private readonly EntityDataSourceState _entityDataSourceState;
37 private List<EntityDataSourceEntitySetNameItem> _entitySetNames;
38 private EntityDataSourceEntitySetNameItem _selectedEntitySetName;
39 private List<EntityDataSourceEntityTypeFilterItem> _entityTypeFilters;
40 private EntityDataSourceEntityTypeFilterItem _selectedEntityTypeFilter;
43 // The Data Selection wizard panel can display two kinds of views of the Select property:
44 // (1) Simple Select View: CheckedListBox with a list of available entity type properties
45 // (2) Advanced Select View: TextBox that allows any statement to be entered (no validation)
47 // When either view is visible to the user, the fields shown below for that view should be non-null, and the fields
48 // for the other view should be null.
51 // _selectedEntityTypeProperties contains a set of indexes of properties in _entityTypeProperties
52 private List<string> _entityTypeProperties;
53 private List<int> _selectedEntityTypeProperties;
55 // Advanced Select View
56 private string _select;
59 private bool _enableInsert;
60 private bool _enableUpdate;
61 private bool _enableDelete;
62 private readonly EntityDataSourceWizardForm _wizardForm;
66 internal EntityDataSourceDataSelection(EntityDataSourceDataSelectionPanel panel, EntityDataSourceWizardForm wizard, EntityDataSourceDesignerHelper designerHelper, EntityDataSourceState entityDataSourceState)
69 _panel.Register(this);
70 _helper = designerHelper;
72 _entityDataSourceState = entityDataSourceState;
78 // Event handler to process notifications when a DefaultContainerName is selected on the ObjectContext configuration panel
79 internal void ContainerNameChangedHandler(object sender, EntityDataSourceContainerNameItem newContainerName)
81 // Load the entity sets for this container, don't select anything initially in the list
82 LoadEntitySetNames(newContainerName, null);
84 // Reset the other controls that depend on the value of EntitySet
85 LoadEntityTypeFilters(null, null);
86 LoadSelect(String.Empty);
90 #region Methods to manage temporary state and wizard contents
91 // Used when the wizard is launched, to load existing property values from data source control
92 internal void LoadState()
94 LoadEntitySetNames(_helper.GetEntityContainerItem(_entityDataSourceState.DefaultContainerName), _entityDataSourceState.EntitySetName);
95 LoadEntityTypeFilters(_selectedEntitySetName, _entityDataSourceState.EntityTypeFilter);
96 LoadSelect(_entityDataSourceState.Select);
97 LoadInsertUpdateDelete();
100 // Save current wizard settings back to the EntityDataSourceState
101 internal void SaveState()
104 SaveEntityTypeFilter();
106 SaveInsertUpdateDelete();
107 SaveEnableFlattening();
110 #region EntitySetName
111 // Find the specified entitySetName in the list or add it if it's not there
112 private EntityDataSourceEntitySetNameItem FindEntitySetName(string entitySetName)
114 if (!String.IsNullOrEmpty(entitySetName))
116 EntityDataSourceEntitySetNameItem entitySetToSelect = null;
117 foreach (EntityDataSourceEntitySetNameItem entitySetNameItem in _entitySetNames)
119 // Ignore case here when searching the list for a matching item, but set the temporary state property to the
120 // correctly-cased version from metadata so that if the user clicks Finish, the correct one will be saved. This
121 // allows some flexibility the designer without preserving an incorrectly-cased value that could cause errors at runtime.
122 if (String.Equals(entitySetNameItem.EntitySetName, entitySetName, StringComparison.OrdinalIgnoreCase))
124 entitySetToSelect = entitySetNameItem;
128 // didn't find a matching entityset, so just create a placeholder for one using the specified name and add it to the list
129 if (entitySetToSelect == null)
131 entitySetToSelect = new EntityDataSourceEntitySetNameItem(entitySetName);
132 _entitySetNames.Add(entitySetToSelect);
135 Debug.Assert(entitySetToSelect != null, "expected a non-null EntityDataSourceEntitySetNameItem");
136 return entitySetToSelect;
142 // Populates the EntitySetName combobox with all of the discoverable EntitySets for the specified container.
143 // If the specified entitySetName is not empty, it is added to the list and selected as the initial value
144 // containerNameItem may not be backed by a real EntityContainer, in which case there is no way to look up the EntitySet in metadata
145 // devnote: This method should not automatically reset EntityTypeFilter and Select because it can be used to load the initial state
146 // for the form, in which case we need to preserve any values that are already set on the data source control.
147 private void LoadEntitySetNames(EntityDataSourceContainerNameItem containerNameItem, string entitySetName)
149 // If this is a container that we found in the project's metadata, get a list of EntitySets for that container
150 if (containerNameItem != null && containerNameItem.EntityContainer != null)
152 _entitySetNames = _helper.GetEntitySets(containerNameItem.EntityContainer, false /*sortResults*/);
154 // Try to find the specified entityset in list and add it if it isn't there
155 _selectedEntitySetName = FindEntitySetName(entitySetName);
159 // if this is an unknown container, there is no way to find a list of entitysets from metadata
160 // so just create a new list and placeholder for the specified entityset
161 _entitySetNames = new List<EntityDataSourceEntitySetNameItem>();
162 if (!String.IsNullOrEmpty(entitySetName))
164 _selectedEntitySetName = new EntityDataSourceEntitySetNameItem(entitySetName);
165 _entitySetNames.Add(_selectedEntitySetName);
169 _selectedEntitySetName = null;
173 // Sort the list now, after we may have added one above
174 _entitySetNames.Sort();
176 // Update the controls
177 _panel.SetEntitySetNames(_entitySetNames);
178 _panel.SetSelectedEntitySetName(_selectedEntitySetName);
181 // Set EntitySetName in temporary storage
182 internal void SelectEntitySetName(EntityDataSourceEntitySetNameItem selectedEntitySet)
184 _selectedEntitySetName = selectedEntitySet;
185 // Load the types for the selected EntitySet, don't select one initially
186 LoadEntityTypeFilters(selectedEntitySet, null);
187 // Reinitialize the Select control with a list of properties, don't preserve any existing Select value
188 LoadSelect(String.Empty);
191 private void SaveEntitySetName()
193 if (_selectedEntitySetName != null)
195 _entityDataSourceState.EntitySetName = _selectedEntitySetName.EntitySetName;
199 _entityDataSourceState.EntitySetName = String.Empty;
204 #region EntityTypeFilter
205 // Populate a list with the base type for the EntitySet plus all derived types, and a special entry to indicate no filter
206 // devnote: This method should not automatically reset Select because it can be used to load the initial state
207 // for the form, in which case we need to preserve any values that are already set on the data source control.
208 private void LoadEntityTypeFilters(EntityDataSourceEntitySetNameItem entitySetItem, string entityTypeFilter)
210 // If this is an EntitySet that we found in the project's metadata, get the type information
211 if (entitySetItem != null && entitySetItem.EntitySet != null)
213 _entityTypeFilters = _helper.GetEntityTypeFilters(entitySetItem.EntitySet.ElementType, false /*sortResults*/);
214 // add (None) to the beginning of the list
215 _entityTypeFilters.Insert(0, s_entityTypeFilterNoneItem);
217 // Try to find the specified type in list and add it if it isn't there
218 _selectedEntityTypeFilter = FindEntityTypeFilter(entityTypeFilter);
222 // if this is an unknown EntitySet, there is no way to find a list of types from metadata
223 // so just create a new list and placeholder for the specified type
224 _entityTypeFilters = new List<EntityDataSourceEntityTypeFilterItem>();
225 _entityTypeFilters.Add(s_entityTypeFilterNoneItem);
227 if (!String.IsNullOrEmpty(entityTypeFilter))
229 _selectedEntityTypeFilter = new EntityDataSourceEntityTypeFilterItem(entityTypeFilter);
230 _entityTypeFilters.Add(_selectedEntityTypeFilter);
234 _selectedEntityTypeFilter = s_entityTypeFilterNoneItem;
238 // Sort now after we might have added items above
239 _entityTypeFilters.Sort();
241 // Update the controls
242 _panel.SetEntityTypeFilters(_entityTypeFilters);
243 _panel.SetSelectedEntityTypeFilter(_selectedEntityTypeFilter);
246 // Find the specified entityTypeFilter in the list and add it if it's not there
247 private EntityDataSourceEntityTypeFilterItem FindEntityTypeFilter(string entityTypeFilter)
249 if (!String.IsNullOrEmpty(entityTypeFilter))
251 EntityDataSourceEntityTypeFilterItem typeToSelect = null;
252 foreach (EntityDataSourceEntityTypeFilterItem entityTypeFilterItem in _entityTypeFilters)
254 // Ignore case here when searching the list for a matching item, but set the temporary state property to the
255 // correctly-cased version from metadata so that if the user clicks Finish, the correct one will be saved. This
256 // allows some flexibility the designer without preserving an incorrectly-cased value that could cause errors at runtime.
257 if (String.Equals(entityTypeFilterItem.EntityTypeName, entityTypeFilter, StringComparison.OrdinalIgnoreCase))
259 typeToSelect = entityTypeFilterItem;
263 // didn't find a matching type, so just create a placeholder item and add it to the list
264 if (typeToSelect == null)
266 typeToSelect = new EntityDataSourceEntityTypeFilterItem(entityTypeFilter);
267 _entityTypeFilters.Add(typeToSelect);
271 Debug.Assert(typeToSelect != null, "expected a non-null string for EntityTypeFilter");
275 return s_entityTypeFilterNoneItem;
278 // Set EntityTypeFilter in temporary storage and load the Select property
279 internal void SelectEntityTypeFilter(EntityDataSourceEntityTypeFilterItem selectedEntityTypeFilter)
281 _selectedEntityTypeFilter = selectedEntityTypeFilter;
282 // Reinitialize the Select control with a list of properties, don't preserve any existing Select value
283 LoadSelect(String.Empty);
286 private void SaveEntityTypeFilter()
288 // If (None) is selected, it is the same as an empty string on the data source control
289 if (Object.ReferenceEquals(_selectedEntityTypeFilter, s_entityTypeFilterNoneItem))
291 _entityDataSourceState.EntityTypeFilter = String.Empty;
295 _entityDataSourceState.EntityTypeFilter = _selectedEntityTypeFilter.EntityTypeName;
302 // Load and parse the Select property
303 private void LoadSelect(string select)
305 Debug.Assert(_selectedEntityTypeFilter != null, "_selectedEntityTypeFilter should never be null");
307 EntityType entityType = GetSelectedEntityType();
309 if (entityType != null)
311 // this is a real type from metadata, load its properties
312 _entityTypeProperties = _helper.GetEntityTypeProperties(entityType);
313 // add the 'Select All (Entity Value)' placeholder at the beginning of the list
314 _entityTypeProperties.Insert(0, Strings.Wizard_DataSelectionPanel_SelectAllProperties);
316 // parse the current value for the Select property to see if it can be displayed in the simple CheckedListBox view
317 if (TryParseSelect(select))
321 // Update the controls
322 _panel.SetEntityTypeProperties(_entityTypeProperties, _selectedEntityTypeProperties);
323 UpdateInsertUpdateDeleteState();
326 // else we failed to parse the select into entity type properties on the specified type, so just use the advanced select view
327 } // else can't get a list of properties unless we have a known EntityType
330 // if we don't have a valid entity type or couldn't parse the incoming Select value, just display the advanced TextBox view
331 _entityTypeProperties = null;
332 _selectedEntityTypeProperties = null;
335 // Update the controls
336 _panel.SetSelect(_select);
337 UpdateInsertUpdateDeleteState();
340 // Build a value for the Select property from the selected values in the CheckedListBox
341 // Value will be in the from "it.Property1, it.Property2, it.Property3"
342 private string BuildSelect()
344 Debug.Assert(_selectedEntityTypeProperties != null && _selectedEntityTypeProperties.Count > 0, "expected non-null _selectedEntityTypeProperties with at least one value");
346 // 'Select All (Entity Value)' is the same thing as an empty string for the property
347 if (_selectedEntityTypeProperties[0] == 0)
349 Debug.Assert(_selectedEntityTypeProperties.Count == 1, "'Select All (Entity Value)' should be the only property selected");
353 StringBuilder selectProperties = new StringBuilder();
354 bool addComma = false;
355 foreach (int propertyIndex in _selectedEntityTypeProperties)
359 selectProperties.Append(", ");
366 selectProperties.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", s_itKeyword, EscapePropertyName(_entityTypeProperties[propertyIndex]));
369 return selectProperties.ToString();
372 private static string EscapePropertyName(string propertyName)
374 return "[" + propertyName.Replace("]", "]]") + "]";
377 static string UnescapePropertyName(string name)
379 if (name[0] == '[' && name[name.Length - 1] == ']')
381 return name.Substring(1, name.Length - 2).Replace("]]", "]");
385 // else the property is not escaped at all or is not properly escaped. We can't parse it so just return.
390 // Parses the current Select property on the data source to see if it matches a specific format that we can use to display the properties
391 // in the CheckedListBox in the simple select wizard view
392 private bool TryParseSelect(string currentSelect)
394 bool parseSuccess = false; // gets set to true after the statement has been successfully parsed
395 if (!String.IsNullOrEmpty(currentSelect))
397 // first try to split the string up into pieces divided by commas
398 // expects a format like the following: (extra spaces around the commas should work as well)
399 // "it.KnownPropertyName1, it.KnownPropertyName2, it.KnownPropertyName3"
400 string[] tokenizedSelect = currentSelect.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
402 bool foundUnknownProperty = false;
403 List<int> selectedProperties = new List<int>();
404 foreach (string token in tokenizedSelect)
406 string propertyName = token.Trim();
408 // Does the current property token start with "it."?
409 if (ReadItKeyword(propertyName))
411 // Does the rest of the property token match a known property name for the selected EntityTypeFilter?
412 int propertyIndex = ReadPropertyName(propertyName.Substring(s_itKeyword.Length));
413 if (propertyIndex == -1)
415 // the property was not known, so we can just stop looking
416 foundUnknownProperty = true;
421 // this is a known property, so add its index to the list
422 selectedProperties.Add(propertyIndex);
427 // the property was not known, so we can just stop looking
428 foundUnknownProperty = true;
432 if (!foundUnknownProperty)
434 // if we never found anything unknown, the current list of properties is what we'll use to fill in the CheckedListBox
435 _selectedEntityTypeProperties = selectedProperties;
440 _selectedEntityTypeProperties = null;
445 // if Select is empty, we just want to add 'Select All (Entity Value)' to the list
446 _selectedEntityTypeProperties = new List<int>();
447 _selectedEntityTypeProperties.Add(0);
454 // Determines if the specified propertyName starts with "it." (case-insensitive)
455 private bool ReadItKeyword(string propertyName)
457 // will accept any casing of "it." here, although when the value is saved back to the property, it will be correctly lower-cased
458 return propertyName.StartsWith(s_itKeyword, StringComparison.OrdinalIgnoreCase);
461 // Determines if the specified propertyName matches one of the known properties for the selected type
462 private int ReadPropertyName(string propertyName)
464 for (int propIndex = 0; propIndex < _entityTypeProperties.Count; propIndex++)
466 // Ignore case here when searching the list for a matching item, but set the temporary state property to the
467 // correctly-cased version from metadata so that if the user clicks Finish, the correct one will be saved. This
468 // allows some flexibility the designer without preserving an incorrectly-cased value that could cause errors at runtime.
470 // Does the specified property name exactly match any of the properties for the selected EntityTypeFilter?
471 if (String.Equals(UnescapePropertyName(propertyName), _entityTypeProperties[propIndex], StringComparison.OrdinalIgnoreCase))
480 // Add the specified property to the list of selected entity properties used to build up the Select property
481 internal void SelectEntityProperty(int propertyIndex)
483 _selectedEntityTypeProperties.Add(propertyIndex);
486 internal void ClearAllSelectedProperties()
488 _selectedEntityTypeProperties.Clear();
491 // Remove specified entity property index from the selected list
492 internal void DeselectEntityProperty(int propertyIndex)
494 _selectedEntityTypeProperties.Remove(propertyIndex);
497 // Set Select property to the specified string (used with advanced select view)
498 internal void SelectAdvancedSelect(string select)
503 private void SaveSelect()
507 _entityDataSourceState.Select = _select;
511 Debug.Assert(_selectedEntityTypeProperties != null, "expected _entityTypeProperties to be non-null if _select is null");
512 _entityDataSourceState.Select = BuildSelect();
517 #region EnableInsertUpdateDelete
518 // Load the initial values for EnableInsert/EnableUpdate/EnableDelete CheckBoxes
519 private void LoadInsertUpdateDelete()
521 SelectEnableInsert(_entityDataSourceState.EnableInsert);
522 SelectEnableUpdate(_entityDataSourceState.EnableUpdate);
523 SelectEnableDelete(_entityDataSourceState.EnableDelete);
525 UpdateInsertUpdateDeleteState();
528 // Set EnableDelete in temporary storage
529 internal void SelectEnableDelete(bool enableDelete)
531 _enableDelete = enableDelete;
534 // Set EnableInsert in temporary storage
535 internal void SelectEnableInsert(bool enableInsert)
537 _enableInsert = enableInsert;
540 // Set EnableUpdate in temporary storage
541 internal void SelectEnableUpdate(bool enableUpdate)
543 _enableUpdate = enableUpdate;
546 private void SaveInsertUpdateDelete()
548 _entityDataSourceState.EnableInsert = _enableInsert;
549 _entityDataSourceState.EnableUpdate = _enableUpdate;
550 _entityDataSourceState.EnableDelete = _enableDelete;
554 /// Update the panel control state based on the valued of enableInsert,
555 /// enableUpdate, enableDelete, and the selectedEntityTypeProperties
557 internal void UpdateInsertUpdateDeleteState()
559 // Set the checkbox state for the panel controls
560 _panel.SetEnableInsertUpdateDelete(_enableInsert, _enableUpdate, _enableDelete);
562 // The InsertUpdateDelete panel should be enabled if:
563 // 1. Insert, Update, or Delete is selected -OR-
564 // 2. The EntitySelection has SelectAll checked
565 bool enablePanel = (_enableInsert || _enableUpdate || _enableDelete ||
566 (_selectedEntityTypeProperties != null &&
567 _selectedEntityTypeProperties.Count == 1 &&
568 _selectedEntityTypeProperties[0] == 0));
570 _panel.SetEnableInsertUpdateDeletePanel(enablePanel);
574 #region EnableFlattening
576 private EntityType GetSelectedEntityType()
578 EntityType entityType = null;
580 // determine which EntityType to load properties for, based on the value selected for EntityTypeFilter
581 if (Object.ReferenceEquals(_selectedEntityTypeFilter, s_entityTypeFilterNoneItem))
583 // If (None) is selected, use the base type for the EntitySet if available
584 if (_selectedEntitySetName != null && _selectedEntitySetName.EntitySet != null)
586 entityType = _selectedEntitySetName.EntitySet.ElementType;
588 // else the EntitySet base type is not known
592 entityType = _selectedEntityTypeFilter.EntityType; // could still be null if the type if not known in metadata
598 private void SaveEnableFlattening()
600 bool enableFlattening = false;
602 EntityType entityType = GetSelectedEntityType();
604 if (entityType != null)
606 foreach (EdmMember member in entityType.Members)
608 // If there is a complex member, enable flattening
609 if (member.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.ComplexType)
611 enableFlattening = true;
614 else if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty)
616 NavigationProperty navProp = (NavigationProperty)member;
617 if (navProp.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)
619 AssociationType associationType = navProp.ToEndMember.DeclaringType as AssociationType;
620 if (!associationType.IsForeignKey)
622 // If there is an independent association, enable flattening
623 enableFlattening = true;
633 enableFlattening = true;
636 _entityDataSourceState.EnableFlattening = enableFlattening;
643 #region Wizard button state management
644 internal void UpdateWizardState()
646 // EntitySetName must be selected and a Select must be configured or must be the empty string
647 _wizardForm.SetCanFinish(_selectedEntitySetName != null && (_select != null || _selectedEntityTypeProperties.Count > 0));