2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <copyright file="EntityDataSourceDataSelection.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
2017-08-21 15:34:15 +00:00
// @owner Microsoft
// @backupOwner Microsoft
2016-08-03 10:59:49 +00:00
//
// Manages the properties that can be set on the second page of the wizard
//------------------------------------------------------------------------------
namespace System.Web.UI.Design.WebControls
{
using System.Collections.Generic ;
using System.Data.Metadata.Edm ;
using System.Diagnostics ;
using System.Globalization ;
using System.Text ;
internal class EntityDataSourceDataSelection
{
#region Private static fields
// iterator prefix used in building and parsing Select property value
private static readonly string s_itKeyword = "it." ;
// Placeholder item to indicate (None) on the EntityTypeFilter ComboBox
private static readonly EntityDataSourceEntityTypeFilterItem s_entityTypeFilterNoneItem =
new EntityDataSourceEntityTypeFilterItem ( Strings . Wizard_DataSelectionPanel_NoEntityTypeFilter ) ;
#endregion
#region Private readonly fields
private readonly EntityDataSourceDataSelectionPanel _panel ;
private readonly EntityDataSourceDesignerHelper _helper ;
#endregion
#region Private fields for temporary storage of property values
private readonly EntityDataSourceState _entityDataSourceState ;
private List < EntityDataSourceEntitySetNameItem > _entitySetNames ;
private EntityDataSourceEntitySetNameItem _selectedEntitySetName ;
private List < EntityDataSourceEntityTypeFilterItem > _entityTypeFilters ;
private EntityDataSourceEntityTypeFilterItem _selectedEntityTypeFilter ;
#region Select views
// The Data Selection wizard panel can display two kinds of views of the Select property:
// (1) Simple Select View: CheckedListBox with a list of available entity type properties
// (2) Advanced Select View: TextBox that allows any statement to be entered (no validation)
//
// When either view is visible to the user, the fields shown below for that view should be non-null, and the fields
// for the other view should be null.
// Simple Select View
// _selectedEntityTypeProperties contains a set of indexes of properties in _entityTypeProperties
private List < string > _entityTypeProperties ;
private List < int > _selectedEntityTypeProperties ;
// Advanced Select View
private string _select ;
#endregion
private bool _enableInsert ;
private bool _enableUpdate ;
private bool _enableDelete ;
private readonly EntityDataSourceWizardForm _wizardForm ;
#endregion
#region Constructors
internal EntityDataSourceDataSelection ( EntityDataSourceDataSelectionPanel panel , EntityDataSourceWizardForm wizard , EntityDataSourceDesignerHelper designerHelper , EntityDataSourceState entityDataSourceState )
{
_panel = panel ;
_panel . Register ( this ) ;
_helper = designerHelper ;
_entityDataSourceState = entityDataSourceState ;
_wizardForm = wizard ;
}
#endregion
#region Events
// Event handler to process notifications when a DefaultContainerName is selected on the ObjectContext configuration panel
internal void ContainerNameChangedHandler ( object sender , EntityDataSourceContainerNameItem newContainerName )
{
// Load the entity sets for this container, don't select anything initially in the list
LoadEntitySetNames ( newContainerName , null ) ;
// Reset the other controls that depend on the value of EntitySet
LoadEntityTypeFilters ( null , null ) ;
LoadSelect ( String . Empty ) ;
}
#endregion
#region Methods to manage temporary state and wizard contents
// Used when the wizard is launched, to load existing property values from data source control
internal void LoadState ( )
{
LoadEntitySetNames ( _helper . GetEntityContainerItem ( _entityDataSourceState . DefaultContainerName ) , _entityDataSourceState . EntitySetName ) ;
LoadEntityTypeFilters ( _selectedEntitySetName , _entityDataSourceState . EntityTypeFilter ) ;
LoadSelect ( _entityDataSourceState . Select ) ;
LoadInsertUpdateDelete ( ) ;
}
// Save current wizard settings back to the EntityDataSourceState
internal void SaveState ( )
{
SaveEntitySetName ( ) ;
SaveEntityTypeFilter ( ) ;
SaveSelect ( ) ;
SaveInsertUpdateDelete ( ) ;
SaveEnableFlattening ( ) ;
}
#region EntitySetName
// Find the specified entitySetName in the list or add it if it's not there
private EntityDataSourceEntitySetNameItem FindEntitySetName ( string entitySetName )
{
if ( ! String . IsNullOrEmpty ( entitySetName ) )
{
EntityDataSourceEntitySetNameItem entitySetToSelect = null ;
foreach ( EntityDataSourceEntitySetNameItem entitySetNameItem in _entitySetNames )
{
// Ignore case here when searching the list for a matching item, but set the temporary state property to the
// correctly-cased version from metadata so that if the user clicks Finish, the correct one will be saved. This
// allows some flexibility the designer without preserving an incorrectly-cased value that could cause errors at runtime.
if ( String . Equals ( entitySetNameItem . EntitySetName , entitySetName , StringComparison . OrdinalIgnoreCase ) )
{
entitySetToSelect = entitySetNameItem ;
}
}
// didn't find a matching entityset, so just create a placeholder for one using the specified name and add it to the list
if ( entitySetToSelect = = null )
{
entitySetToSelect = new EntityDataSourceEntitySetNameItem ( entitySetName ) ;
_entitySetNames . Add ( entitySetToSelect ) ;
}
Debug . Assert ( entitySetToSelect ! = null , "expected a non-null EntityDataSourceEntitySetNameItem" ) ;
return entitySetToSelect ;
}
return null ;
}
// Populates the EntitySetName combobox with all of the discoverable EntitySets for the specified container.
// If the specified entitySetName is not empty, it is added to the list and selected as the initial value
// containerNameItem may not be backed by a real EntityContainer, in which case there is no way to look up the EntitySet in metadata
// devnote: This method should not automatically reset EntityTypeFilter and Select because it can be used to load the initial state
// for the form, in which case we need to preserve any values that are already set on the data source control.
private void LoadEntitySetNames ( EntityDataSourceContainerNameItem containerNameItem , string entitySetName )
{
// If this is a container that we found in the project's metadata, get a list of EntitySets for that container
if ( containerNameItem ! = null & & containerNameItem . EntityContainer ! = null )
{
_entitySetNames = _helper . GetEntitySets ( containerNameItem . EntityContainer , false /*sortResults*/ ) ;
// Try to find the specified entityset in list and add it if it isn't there
_selectedEntitySetName = FindEntitySetName ( entitySetName ) ;
}
else
{
// if this is an unknown container, there is no way to find a list of entitysets from metadata
// so just create a new list and placeholder for the specified entityset
_entitySetNames = new List < EntityDataSourceEntitySetNameItem > ( ) ;
if ( ! String . IsNullOrEmpty ( entitySetName ) )
{
_selectedEntitySetName = new EntityDataSourceEntitySetNameItem ( entitySetName ) ;
_entitySetNames . Add ( _selectedEntitySetName ) ;
}
else
{
_selectedEntitySetName = null ;
}
}
// Sort the list now, after we may have added one above
_entitySetNames . Sort ( ) ;
// Update the controls
_panel . SetEntitySetNames ( _entitySetNames ) ;
_panel . SetSelectedEntitySetName ( _selectedEntitySetName ) ;
}
// Set EntitySetName in temporary storage
internal void SelectEntitySetName ( EntityDataSourceEntitySetNameItem selectedEntitySet )
{
_selectedEntitySetName = selectedEntitySet ;
// Load the types for the selected EntitySet, don't select one initially
LoadEntityTypeFilters ( selectedEntitySet , null ) ;
// Reinitialize the Select control with a list of properties, don't preserve any existing Select value
LoadSelect ( String . Empty ) ;
}
private void SaveEntitySetName ( )
{
if ( _selectedEntitySetName ! = null )
{
_entityDataSourceState . EntitySetName = _selectedEntitySetName . EntitySetName ;
}
else
{
_entityDataSourceState . EntitySetName = String . Empty ;
}
}
#endregion
#region EntityTypeFilter
// Populate a list with the base type for the EntitySet plus all derived types, and a special entry to indicate no filter
// devnote: This method should not automatically reset Select because it can be used to load the initial state
// for the form, in which case we need to preserve any values that are already set on the data source control.
private void LoadEntityTypeFilters ( EntityDataSourceEntitySetNameItem entitySetItem , string entityTypeFilter )
{
// If this is an EntitySet that we found in the project's metadata, get the type information
if ( entitySetItem ! = null & & entitySetItem . EntitySet ! = null )
{
_entityTypeFilters = _helper . GetEntityTypeFilters ( entitySetItem . EntitySet . ElementType , false /*sortResults*/ ) ;
// add (None) to the beginning of the list
_entityTypeFilters . Insert ( 0 , s_entityTypeFilterNoneItem ) ;
// Try to find the specified type in list and add it if it isn't there
_selectedEntityTypeFilter = FindEntityTypeFilter ( entityTypeFilter ) ;
}
else
{
// if this is an unknown EntitySet, there is no way to find a list of types from metadata
// so just create a new list and placeholder for the specified type
_entityTypeFilters = new List < EntityDataSourceEntityTypeFilterItem > ( ) ;
_entityTypeFilters . Add ( s_entityTypeFilterNoneItem ) ;
if ( ! String . IsNullOrEmpty ( entityTypeFilter ) )
{
_selectedEntityTypeFilter = new EntityDataSourceEntityTypeFilterItem ( entityTypeFilter ) ;
_entityTypeFilters . Add ( _selectedEntityTypeFilter ) ;
}
else
{
_selectedEntityTypeFilter = s_entityTypeFilterNoneItem ;
}
}
// Sort now after we might have added items above
_entityTypeFilters . Sort ( ) ;
// Update the controls
_panel . SetEntityTypeFilters ( _entityTypeFilters ) ;
_panel . SetSelectedEntityTypeFilter ( _selectedEntityTypeFilter ) ;
}
// Find the specified entityTypeFilter in the list and add it if it's not there
private EntityDataSourceEntityTypeFilterItem FindEntityTypeFilter ( string entityTypeFilter )
{
if ( ! String . IsNullOrEmpty ( entityTypeFilter ) )
{
EntityDataSourceEntityTypeFilterItem typeToSelect = null ;
foreach ( EntityDataSourceEntityTypeFilterItem entityTypeFilterItem in _entityTypeFilters )
{
// Ignore case here when searching the list for a matching item, but set the temporary state property to the
// correctly-cased version from metadata so that if the user clicks Finish, the correct one will be saved. This
// allows some flexibility the designer without preserving an incorrectly-cased value that could cause errors at runtime.
if ( String . Equals ( entityTypeFilterItem . EntityTypeName , entityTypeFilter , StringComparison . OrdinalIgnoreCase ) )
{
typeToSelect = entityTypeFilterItem ;
}
}
// didn't find a matching type, so just create a placeholder item and add it to the list
if ( typeToSelect = = null )
{
typeToSelect = new EntityDataSourceEntityTypeFilterItem ( entityTypeFilter ) ;
_entityTypeFilters . Add ( typeToSelect ) ;
}
Debug . Assert ( typeToSelect ! = null , "expected a non-null string for EntityTypeFilter" ) ;
return typeToSelect ;
}
return s_entityTypeFilterNoneItem ;
}
// Set EntityTypeFilter in temporary storage and load the Select property
internal void SelectEntityTypeFilter ( EntityDataSourceEntityTypeFilterItem selectedEntityTypeFilter )
{
_selectedEntityTypeFilter = selectedEntityTypeFilter ;
// Reinitialize the Select control with a list of properties, don't preserve any existing Select value
LoadSelect ( String . Empty ) ;
}
private void SaveEntityTypeFilter ( )
{
// If (None) is selected, it is the same as an empty string on the data source control
if ( Object . ReferenceEquals ( _selectedEntityTypeFilter , s_entityTypeFilterNoneItem ) )
{
_entityDataSourceState . EntityTypeFilter = String . Empty ;
}
else
{
_entityDataSourceState . EntityTypeFilter = _selectedEntityTypeFilter . EntityTypeName ;
}
}
#endregion
#region Select
// Load and parse the Select property
private void LoadSelect ( string select )
{
Debug . Assert ( _selectedEntityTypeFilter ! = null , "_selectedEntityTypeFilter should never be null" ) ;
EntityType entityType = GetSelectedEntityType ( ) ;
if ( entityType ! = null )
{
// this is a real type from metadata, load its properties
_entityTypeProperties = _helper . GetEntityTypeProperties ( entityType ) ;
// add the 'Select All (Entity Value)' placeholder at the beginning of the list
_entityTypeProperties . Insert ( 0 , Strings . Wizard_DataSelectionPanel_SelectAllProperties ) ;
// parse the current value for the Select property to see if it can be displayed in the simple CheckedListBox view
if ( TryParseSelect ( select ) )
{
_select = null ;
// Update the controls
_panel . SetEntityTypeProperties ( _entityTypeProperties , _selectedEntityTypeProperties ) ;
UpdateInsertUpdateDeleteState ( ) ;
return ;
}
// else we failed to parse the select into entity type properties on the specified type, so just use the advanced select view
} // else can't get a list of properties unless we have a known EntityType
// if we don't have a valid entity type or couldn't parse the incoming Select value, just display the advanced TextBox view
_entityTypeProperties = null ;
_selectedEntityTypeProperties = null ;
_select = select ;
// Update the controls
_panel . SetSelect ( _select ) ;
UpdateInsertUpdateDeleteState ( ) ;
}
// Build a value for the Select property from the selected values in the CheckedListBox
// Value will be in the from "it.Property1, it.Property2, it.Property3"
private string BuildSelect ( )
{
Debug . Assert ( _selectedEntityTypeProperties ! = null & & _selectedEntityTypeProperties . Count > 0 , "expected non-null _selectedEntityTypeProperties with at least one value" ) ;
// 'Select All (Entity Value)' is the same thing as an empty string for the property
if ( _selectedEntityTypeProperties [ 0 ] = = 0 )
{
Debug . Assert ( _selectedEntityTypeProperties . Count = = 1 , "'Select All (Entity Value)' should be the only property selected" ) ;
return String . Empty ;
}
StringBuilder selectProperties = new StringBuilder ( ) ;
bool addComma = false ;
foreach ( int propertyIndex in _selectedEntityTypeProperties )
{
if ( addComma )
{
selectProperties . Append ( ", " ) ;
}
else
{
addComma = true ;
}
selectProperties . AppendFormat ( CultureInfo . InvariantCulture , "{0}{1}" , s_itKeyword , EscapePropertyName ( _entityTypeProperties [ propertyIndex ] ) ) ;
}
return selectProperties . ToString ( ) ;
}
private static string EscapePropertyName ( string propertyName )
{
return "[" + propertyName . Replace ( "]" , "]]" ) + "]" ;
}
static string UnescapePropertyName ( string name )
{
if ( name [ 0 ] = = '[' & & name [ name . Length - 1 ] = = ']' )
{
return name . Substring ( 1 , name . Length - 2 ) . Replace ( "]]" , "]" ) ;
}
else
{
// else the property is not escaped at all or is not properly escaped. We can't parse it so just return.
return name ;
}
}
// 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
// in the CheckedListBox in the simple select wizard view
private bool TryParseSelect ( string currentSelect )
{
bool parseSuccess = false ; // gets set to true after the statement has been successfully parsed
if ( ! String . IsNullOrEmpty ( currentSelect ) )
{
// first try to split the string up into pieces divided by commas
// expects a format like the following: (extra spaces around the commas should work as well)
// "it.KnownPropertyName1, it.KnownPropertyName2, it.KnownPropertyName3"
string [ ] tokenizedSelect = currentSelect . Split ( new char [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
bool foundUnknownProperty = false ;
List < int > selectedProperties = new List < int > ( ) ;
foreach ( string token in tokenizedSelect )
{
string propertyName = token . Trim ( ) ;
// Does the current property token start with "it."?
if ( ReadItKeyword ( propertyName ) )
{
// Does the rest of the property token match a known property name for the selected EntityTypeFilter?
int propertyIndex = ReadPropertyName ( propertyName . Substring ( s_itKeyword . Length ) ) ;
if ( propertyIndex = = - 1 )
{
// the property was not known, so we can just stop looking
foundUnknownProperty = true ;
break ;
}
else
{
// this is a known property, so add its index to the list
selectedProperties . Add ( propertyIndex ) ;
}
}
else
{
// the property was not known, so we can just stop looking
foundUnknownProperty = true ;
break ;
}
}
if ( ! foundUnknownProperty )
{
// if we never found anything unknown, the current list of properties is what we'll use to fill in the CheckedListBox
_selectedEntityTypeProperties = selectedProperties ;
parseSuccess = true ;
}
else
{
_selectedEntityTypeProperties = null ;
}
}
else
{
// if Select is empty, we just want to add 'Select All (Entity Value)' to the list
_selectedEntityTypeProperties = new List < int > ( ) ;
_selectedEntityTypeProperties . Add ( 0 ) ;
parseSuccess = true ;
}
return parseSuccess ;
}
// Determines if the specified propertyName starts with "it." (case-insensitive)
private bool ReadItKeyword ( string propertyName )
{
// will accept any casing of "it." here, although when the value is saved back to the property, it will be correctly lower-cased
return propertyName . StartsWith ( s_itKeyword , StringComparison . OrdinalIgnoreCase ) ;
}
// Determines if the specified propertyName matches one of the known properties for the selected type
private int ReadPropertyName ( string propertyName )
{
for ( int propIndex = 0 ; propIndex < _entityTypeProperties . Count ; propIndex + + )
{
// Ignore case here when searching the list for a matching item, but set the temporary state property to the
// correctly-cased version from metadata so that if the user clicks Finish, the correct one will be saved. This
// allows some flexibility the designer without preserving an incorrectly-cased value that could cause errors at runtime.
// Does the specified property name exactly match any of the properties for the selected EntityTypeFilter?
if ( String . Equals ( UnescapePropertyName ( propertyName ) , _entityTypeProperties [ propIndex ] , StringComparison . OrdinalIgnoreCase ) )
{
return propIndex ;
}
}
return - 1 ;
}
// Add the specified property to the list of selected entity properties used to build up the Select property
internal void SelectEntityProperty ( int propertyIndex )
{
_selectedEntityTypeProperties . Add ( propertyIndex ) ;
}
internal void ClearAllSelectedProperties ( )
{
_selectedEntityTypeProperties . Clear ( ) ;
}
// Remove specified entity property index from the selected list
internal void DeselectEntityProperty ( int propertyIndex )
{
_selectedEntityTypeProperties . Remove ( propertyIndex ) ;
}
// Set Select property to the specified string (used with advanced select view)
internal void SelectAdvancedSelect ( string select )
{
_select = select ;
}
private void SaveSelect ( )
{
if ( _select ! = null )
{
_entityDataSourceState . Select = _select ;
}
else
{
Debug . Assert ( _selectedEntityTypeProperties ! = null , "expected _entityTypeProperties to be non-null if _select is null" ) ;
_entityDataSourceState . Select = BuildSelect ( ) ;
}
}
#endregion
#region EnableInsertUpdateDelete
// Load the initial values for EnableInsert/EnableUpdate/EnableDelete CheckBoxes
private void LoadInsertUpdateDelete ( )
{
SelectEnableInsert ( _entityDataSourceState . EnableInsert ) ;
SelectEnableUpdate ( _entityDataSourceState . EnableUpdate ) ;
SelectEnableDelete ( _entityDataSourceState . EnableDelete ) ;
UpdateInsertUpdateDeleteState ( ) ;
}
// Set EnableDelete in temporary storage
internal void SelectEnableDelete ( bool enableDelete )
{
_enableDelete = enableDelete ;
}
// Set EnableInsert in temporary storage
internal void SelectEnableInsert ( bool enableInsert )
{
_enableInsert = enableInsert ;
}
// Set EnableUpdate in temporary storage
internal void SelectEnableUpdate ( bool enableUpdate )
{
_enableUpdate = enableUpdate ;
}
private void SaveInsertUpdateDelete ( )
{
_entityDataSourceState . EnableInsert = _enableInsert ;
_entityDataSourceState . EnableUpdate = _enableUpdate ;
_entityDataSourceState . EnableDelete = _enableDelete ;
}
/// <summary>
/// Update the panel control state based on the valued of enableInsert,
/// enableUpdate, enableDelete, and the selectedEntityTypeProperties
/// </summary>
internal void UpdateInsertUpdateDeleteState ( )
{
// Set the checkbox state for the panel controls
_panel . SetEnableInsertUpdateDelete ( _enableInsert , _enableUpdate , _enableDelete ) ;
// The InsertUpdateDelete panel should be enabled if:
// 1. Insert, Update, or Delete is selected -OR-
// 2. The EntitySelection has SelectAll checked
bool enablePanel = ( _enableInsert | | _enableUpdate | | _enableDelete | |
( _selectedEntityTypeProperties ! = null & &
_selectedEntityTypeProperties . Count = = 1 & &
_selectedEntityTypeProperties [ 0 ] = = 0 ) ) ;
_panel . SetEnableInsertUpdateDeletePanel ( enablePanel ) ;
}
#endregion
#region EnableFlattening
private EntityType GetSelectedEntityType ( )
{
EntityType entityType = null ;
// determine which EntityType to load properties for, based on the value selected for EntityTypeFilter
if ( Object . ReferenceEquals ( _selectedEntityTypeFilter , s_entityTypeFilterNoneItem ) )
{
// If (None) is selected, use the base type for the EntitySet if available
if ( _selectedEntitySetName ! = null & & _selectedEntitySetName . EntitySet ! = null )
{
entityType = _selectedEntitySetName . EntitySet . ElementType ;
}
// else the EntitySet base type is not known
}
else
{
entityType = _selectedEntityTypeFilter . EntityType ; // could still be null if the type if not known in metadata
}
return entityType ;
}
private void SaveEnableFlattening ( )
{
bool enableFlattening = false ;
EntityType entityType = GetSelectedEntityType ( ) ;
if ( entityType ! = null )
{
foreach ( EdmMember member in entityType . Members )
{
// If there is a complex member, enable flattening
if ( member . TypeUsage . EdmType . BuiltInTypeKind = = BuiltInTypeKind . ComplexType )
{
enableFlattening = true ;
break ;
}
else if ( member . BuiltInTypeKind = = BuiltInTypeKind . NavigationProperty )
{
NavigationProperty navProp = ( NavigationProperty ) member ;
if ( navProp . ToEndMember . RelationshipMultiplicity ! = RelationshipMultiplicity . Many )
{
AssociationType associationType = navProp . ToEndMember . DeclaringType as AssociationType ;
if ( ! associationType . IsForeignKey )
{
// If there is an independent association, enable flattening
enableFlattening = true ;
break ;
}
}
}
}
}
else
{
// Projection
enableFlattening = true ;
}
_entityDataSourceState . EnableFlattening = enableFlattening ;
}
#endregion
#endregion
#region Wizard button state management
internal void UpdateWizardState ( )
{
// EntitySetName must be selected and a Select must be configured or must be the empty string
_wizardForm . SetCanFinish ( _selectedEntitySetName ! = null & & ( _select ! = null | | _selectedEntityTypeProperties . Count > 0 ) ) ;
}
#endregion
}
}