2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <copyright file="EntityDataSourceDesignerHelper.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
//------------------------------------------------------------------------------
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Configuration ;
using System.ComponentModel.Design ;
using System.Data ;
using System.Data.Common ;
using System.Data.EntityClient ;
using System.Data.Mapping ;
using System.Data.Metadata.Edm ;
using System.Web.UI.Design.WebControls.Util ;
using System.Diagnostics ;
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
using System.Text ;
using System.Web.Configuration ;
using System.Web.UI ;
using System.Web.UI.Design ;
using System.Web.UI.WebControls ;
using System.Windows.Forms ;
using System.Xml ;
namespace System.Web.UI.Design.WebControls
{
internal class EntityDataSourceDesignerHelper
{
#region Private and Internal static constants
private static readonly string s_virtualRoot = "~/" ;
private static readonly string s_ecmPublicKeyToken = "PublicKeyToken=" + AssemblyRef . EcmaPublicKey ;
private static readonly string s_entityClientProviderName = "System.Data.EntityClient" ;
private static readonly string s_metadataPathSeparator = "|" ;
private static readonly string s_resPathPrefix = "res://" ;
private static readonly string s_relativeParentFolder = "../" ;
private static readonly string s_relativeCurrentFolder = "./" ;
private static readonly string s_altRelativeParentFolder = @"..\" ;
private static readonly string s_altRelativeCurrentFolder = @".\" ;
private static readonly string s_dataDirectoryNoPipes = "DataDirectory" ;
private static readonly string s_dataDirectory = "|DataDirectory|" ;
private static readonly string s_dataDirectoryPath = String . Concat ( s_virtualRoot , "app_data" ) ;
private static readonly string s_resolvedResPathFormat = String . Concat ( s_resPathPrefix , "{0}/{1}" ) ;
private static readonly string DesignerStateDataSourceSchemaKey = "EntityDataSourceSchema" ;
private static readonly string DesignerStateDataSourceConnectionStringKey = "EntityDataSourceConnectionString" ;
private static readonly string DesignerStateDataSourceDefaultContainerNameKey = "EntityDataSourceDefaultContainerName" ;
private static readonly string DesignerStateDataSourceEntitySetNameKey = "EntityDataSourceEntitySetNameKey" ;
private static readonly string DesignerStateDataSourceSelectKey = "EntityDataSourceSelectKey" ;
private static readonly string DesignerStateDataSourceCommandTextKey = "EntityDataSourceCommandTextKey" ;
private static readonly string DesignerStateDataSourceEnableFlatteningKey = "EntityDataSourceEnableFlattening" ;
internal static readonly string DefaultViewName = "DefaultView" ;
#endregion
#region Private instance fields
private readonly EntityDataSource _entityDataSource ;
private EntityConnection _entityConnection ;
private readonly IWebApplication _webApplication ;
// determines if any errors or warnings are displayed and if the EntityConnection and metadata are automatically loaded when accessed
private bool _interactiveMode ;
private HashSet < Assembly > _assemblies ;
private EntityDesignerDataSourceView _view ;
private bool _forceSchemaRetrieval ;
private readonly EntityDataSourceDesigner _owner ;
private bool _canLoadWebConfig ;
private bool _usingEntityFrameworkVersionHigherThanFive = false ;
#endregion
internal EntityDataSourceDesignerHelper ( EntityDataSource entityDataSource , bool interactiveMode )
{
Debug . Assert ( entityDataSource ! = null , "null entityDataSource" ) ;
_entityDataSource = entityDataSource ;
_interactiveMode = interactiveMode ;
_canLoadWebConfig = true ;
IServiceProvider serviceProvider = _entityDataSource . Site ;
if ( serviceProvider ! = null )
{
// Get the designer instance associated with the specified data source control
IDesignerHost designerHost = ( IDesignerHost ) serviceProvider . GetService ( typeof ( IDesignerHost ) ) ;
if ( designerHost ! = null )
{
_owner = designerHost . GetDesigner ( this . EntityDataSource ) as EntityDataSourceDesigner ;
}
// Get other services used to determine design-time information
_webApplication = serviceProvider . GetService ( typeof ( IWebApplication ) ) as IWebApplication ;
}
Debug . Assert ( _owner ! = null , "expected non-null owner" ) ;
Debug . Assert ( _webApplication ! = null , "expected non-null web application service" ) ;
}
internal void AddSystemWebEntityReference ( )
{
IServiceProvider serviceProvider = _entityDataSource . Site ;
if ( serviceProvider ! = null )
{
ITypeResolutionService typeResProvider = ( ITypeResolutionService ) serviceProvider . GetService ( typeof ( ITypeResolutionService ) ) ;
if ( typeResProvider ! = null )
{
try
{
// Adding the reference using just the name and public key since we don't want to be
// tied to a particular version here.
typeResProvider . ReferenceAssembly (
new AssemblyName ( "System.Web.Entity,PublicKeyToken=" + AssemblyRef . EcmaPublicKey ) ) ;
}
catch ( FileNotFoundException )
{
Debug . Fail ( "Failed to find System.Web.Entity assembly." ) ;
// Intentionally ignored exception - the assembly should always be
// found, but if it isn't, then we don't want to stop the rest of the
// control from working, especially since the assembly may not always
// be required.
}
}
}
}
#region Helpers for EntityDataSource properties
internal bool AutoGenerateWhereClause
{
get
{
return _entityDataSource . AutoGenerateWhereClause ;
}
}
internal bool AutoGenerateOrderByClause
{
get
{
return _entityDataSource . AutoGenerateOrderByClause ;
}
}
internal bool CanPage
{
get
{
DataSourceView view = ( ( IDataSource ) _entityDataSource ) . GetView ( DefaultViewName ) ;
if ( view ! = null )
{
return view . CanPage ;
}
return false ;
}
}
internal bool CanSort
{
get
{
DataSourceView view = ( ( IDataSource ) _entityDataSource ) . GetView ( DefaultViewName ) ;
if ( view ! = null )
{
return view . CanSort ;
}
return false ;
}
}
internal string ConnectionString
{
get
{
return _entityDataSource . ConnectionString ;
}
set
{
if ( value ! = ConnectionString )
{
_entityDataSource . ConnectionString = value ;
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
}
internal string CommandText
{
get
{
return _entityDataSource . CommandText ;
}
set
{
if ( value ! = CommandText )
{
_entityDataSource . CommandText = value ;
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
}
internal ParameterCollection CommandParameters
{
get
{
return _entityDataSource . CommandParameters ;
}
}
internal string DefaultContainerName
{
get
{
return _entityDataSource . DefaultContainerName ;
}
set
{
if ( value ! = DefaultContainerName )
{
_entityDataSource . DefaultContainerName = value ;
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
}
internal bool EnableDelete
{
get
{
return _entityDataSource . EnableDelete ;
}
}
internal bool EnableInsert
{
get
{
return _entityDataSource . EnableInsert ;
}
}
internal bool EnableUpdate
{
get
{
return _entityDataSource . EnableUpdate ;
}
}
internal bool EnableFlattening
{
get
{
return _entityDataSource . EnableFlattening ;
}
}
internal string EntitySetName
{
get
{
return _entityDataSource . EntitySetName ;
}
set
{
if ( value ! = EntitySetName )
{
_entityDataSource . EntitySetName = value ;
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
}
internal string EntityTypeFilter
{
get
{
return _entityDataSource . EntityTypeFilter ;
}
set
{
if ( value ! = EntityTypeFilter )
{
_entityDataSource . EntityTypeFilter = value ;
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
}
internal string GroupBy
{
get
{
return _entityDataSource . GroupBy ;
}
}
internal string OrderBy
{
get
{
return _entityDataSource . OrderBy ;
}
set
{
if ( value ! = OrderBy )
{
_entityDataSource . OrderBy = value ;
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
}
internal ParameterCollection OrderByParameters
{
get
{
return _entityDataSource . OrderByParameters ;
}
}
internal string Select
{
get
{
return _entityDataSource . Select ;
}
set
{
if ( value ! = Select )
{
_entityDataSource . Select = value ;
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
}
internal ParameterCollection SelectParameters
{
get
{
return _entityDataSource . SelectParameters ;
}
}
internal string Where
{
get
{
return _entityDataSource . Where ;
}
set
{
if ( value ! = Where )
{
_entityDataSource . Where = value ;
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
}
internal ParameterCollection WhereParameters
{
get
{
return _entityDataSource . WhereParameters ;
}
}
#endregion
private EntityDataSource EntityDataSource
{
get
{
return _entityDataSource ;
}
}
private EdmItemCollection EdmItemCollection
{
get
{
// In interactive mode, we will explicitly load metadata when needed, so never load it here
// When not in interactive mode, we only need to load metadata once, which is determined by the presence of an EntityConnection
if ( ! _interactiveMode & & _entityConnection = = null )
{
LoadMetadata ( ) ;
}
// _entityConnection may still be null if the load failed or if we are in interactive mode and the metadata has not been explicitly loaded
if ( _entityConnection ! = null )
{
ItemCollection itemCollection = null ;
try
{
_entityConnection . GetMetadataWorkspace ( ) . TryGetItemCollection ( DataSpace . CSpace , out itemCollection ) ;
}
catch ( Exception )
{
// Never expecting a failure because we have already initialized the workspace when the metadata was loaded,
// and any errors would have been trapped then. Just ignore any errors that might occur here to prevent a crash.
}
return itemCollection as EdmItemCollection ; // not guaranteed not to be null, caller must check anyway before using
}
return null ;
}
}
// The default DesignerDataSourceView
private EntityDesignerDataSourceView View
{
get
{
return _view ;
}
set
{
_view = value ;
}
}
/// <summary>
/// The status of loading the web.config file for named connections.
/// This helps to determine if we've already tried to load the web.config file and if it failed
/// we do not continue to load it again.
/// </summary>
internal bool CanLoadWebConfig
{
get
{
return _canLoadWebConfig ;
}
set
{
_canLoadWebConfig = value ;
}
}
// Whether or not a schema retrieval is being forced (used in RefreshSchema)
private bool ForceSchemaRetrieval
{
get
{
return _forceSchemaRetrieval ;
}
set
{
_forceSchemaRetrieval = value ;
}
}
// Loads metadata for the current connection string on the data source control
private bool LoadMetadata ( )
{
EntityConnectionStringBuilder connStrBuilder = VerifyConnectionString ( this . EntityDataSource . ConnectionString , true /*allowNamedConnections*/ ) ;
if ( connStrBuilder ! = null )
{
return LoadMetadata ( connStrBuilder ) ;
}
// else the connection string could not be verified, and any errors are already displayed during the failed verification, so nothing more to do
return false ;
}
internal bool LoadMetadata ( EntityConnectionStringBuilder connStrBuilder )
{
Debug . Assert ( connStrBuilder ! = null , "expected non-null connStrBuilder" ) ;
// if these services are not available for some reason, we will not be able to do anything useful, so don't try to load metadata
if ( _webApplication ! = null )
{
// _assemblies could already be loaded if this call is coming from the wizard, because metadata could be loaded
// multiple times if the connection string is changed, so don't load it again if we already have it. It can't have
// changed since the last load, because the wizard dialog is modal and there is no way to have changed the project between loads.
if ( _assemblies = = null )
{
LoadAssemblies ( ) ;
}
return LoadMetadataFromBuilder ( connStrBuilder ) ;
}
return false ;
}
// Loads C-Space metadata from the specified connection string builder.
// Expects that the specified builder has already been verified and has minimum required properties
private bool LoadMetadataFromBuilder ( EntityConnectionStringBuilder connStrBuilder )
{
Debug . Assert ( connStrBuilder ! = null , "expected non-null connStrBuilder" ) ;
// We will be replacing the metadata with a new collection, and if something fails to load we want to make sure to clear out any existing data
ClearMetadata ( ) ;
if ( String . IsNullOrEmpty ( connStrBuilder . ConnectionString ) )
{
// Although we can't load metadata here, this is not an error because the user should not expect an empty connection string
// to produce any metadata, so it will not be confusing when no values are available in the dropdowns. This is different from
// an invalid connection string because in that case they have entered a value and are expecting to get metadata from it.
return false ;
}
// If this is a named connection, load the contents of the named connection from the web.config and verify itet
if ( ! String . IsNullOrEmpty ( connStrBuilder . Name ) )
{
connStrBuilder = GetBuilderForNamedConnection ( connStrBuilder ) ;
if ( connStrBuilder = = null )
{
// some verification failed when getting the connection string builder,
// so we have nothing to load metadata from, just return
return false ;
}
}
string originalMetadata = connStrBuilder . Metadata ;
Debug . Assert ( ! String . IsNullOrEmpty ( originalMetadata ) , "originalMetadata should have aleady been verified to be non-null or empty" ) ;
List < string > metadataWarnings = new List < string > ( ) ; // keeps track of all warnings that happen during the parsing of the connection string
List < string > metadataPaths = new List < string > ( ) ; // collection of resolved paths
// We need to use the | separator to split the metadata value into individual paths, so first remove and process any paths
// containing the macro |DataDirectory|. These will get combined again with the rest of the paths once they have been processed.
List < string > dataDirectoryPaths = new List < string > ( ) ;
string metadataWithoutDataDirectory = ResolveDataDirectory ( originalMetadata , dataDirectoryPaths , metadataWarnings ) ;
foreach ( string path in metadataWithoutDataDirectory . Split ( new string [ ] { s_metadataPathSeparator } , StringSplitOptions . RemoveEmptyEntries ) )
{
string trimmedPath = path . Trim ( ) ;
if ( ! String . IsNullOrEmpty ( trimmedPath ) )
{
if ( trimmedPath . StartsWith ( s_virtualRoot , StringComparison . OrdinalIgnoreCase ) )
{
// ~/
ResolveVirtualRootPath ( trimmedPath , metadataPaths , metadataWarnings ) ;
}
else if ( trimmedPath . StartsWith ( s_relativeCurrentFolder , StringComparison . OrdinalIgnoreCase ) | |
trimmedPath . StartsWith ( s_altRelativeCurrentFolder , StringComparison . OrdinalIgnoreCase ) | |
trimmedPath . StartsWith ( s_relativeParentFolder , StringComparison . OrdinalIgnoreCase ) | |
trimmedPath . StartsWith ( s_altRelativeParentFolder , StringComparison . OrdinalIgnoreCase ) )
{
// ../, ..\, ./, or ..\
ResolveRelativePath ( trimmedPath , metadataPaths , metadataWarnings ) ;
}
else
{
// We are not trying to resolve any other types of paths, so just pass it along directly.
// If the format of the path is unrecognized, or if the path is not valid, metadata will throw an exception that will
// be displayed to the user at that time.
metadataPaths . Add ( trimmedPath ) ;
}
}
}
// Add the paths with |DataDirectory| back to the list
if ( dataDirectoryPaths . Count > 0 )
{
metadataPaths . AddRange ( dataDirectoryPaths ) ;
}
if ( metadataWarnings . Count > 0 )
{
ShowWarning ( BuildWarningMessage ( Strings . Warning_ConnectionStringMessageHeader , metadataWarnings ) ) ;
}
return SetEntityConnection ( metadataPaths , connStrBuilder ) ;
}
private bool SetEntityConnection ( List < string > metadataPaths , EntityConnectionStringBuilder connStrBuilder )
{
// It's possible the metadata was specified in the original connection string, but we filtered out everything due to not being able to resolve it to anything.
// In that case, warnings have already been displayed to indicate which paths were removed, so no need to display another message.
if ( metadataPaths . Count > 0 )
{
try
{
// Get the connection first, because it might be needed to gather provider services information
DbConnection dbConnection = GetDbConnection ( connStrBuilder ) ;
MetadataWorkspace metadataWorkspace = new MetadataWorkspace ( metadataPaths , _assemblies ) ;
// Ensure that we have all of the item collections registered. If some of them are missing this will cause problems eventually if we need to
// execute a query to get detailed schema information, but that will be handled later. For now just register everything to prevent errors in the
// stack that would not be understood by the user in the designer at this point.
ItemCollection edmItemCollection ;
ItemCollection storeItemCollection ;
ItemCollection csItemCollection ;
if ( ! metadataWorkspace . TryGetItemCollection ( DataSpace . CSpace , out edmItemCollection ) )
{
edmItemCollection = new EdmItemCollection ( ) ;
metadataWorkspace . RegisterItemCollection ( edmItemCollection ) ;
}
if ( ! metadataWorkspace . TryGetItemCollection ( DataSpace . SSpace , out storeItemCollection ) )
{
return false ;
}
if ( ! metadataWorkspace . TryGetItemCollection ( DataSpace . CSSpace , out csItemCollection ) )
{
Debug . Assert ( edmItemCollection ! = null & & storeItemCollection ! = null , "edm and store ItemCollection should be populated already" ) ;
metadataWorkspace . RegisterItemCollection ( new StorageMappingItemCollection ( edmItemCollection as EdmItemCollection , storeItemCollection as StoreItemCollection ) ) ;
}
// Create an ObjectItemCollection beforehand so that we can load objects by-convention
metadataWorkspace . RegisterItemCollection ( new ObjectItemCollection ( ) ) ;
// Load OSpace metadata from all of the assemblies we know about
foreach ( Assembly assembly in _assemblies )
{
metadataWorkspace . LoadFromAssembly ( assembly ) ;
}
if ( dbConnection ! = null )
{
_entityConnection = new EntityConnection ( metadataWorkspace , dbConnection ) ;
return true ;
}
// else the DbConnection could not be created and the error should have already been displayed
}
catch ( Exception ex )
{
StringBuilder exceptionMessage = new StringBuilder ( ) ;
if ( _usingEntityFrameworkVersionHigherThanFive )
{
exceptionMessage . Append ( Strings . Error_UnsupportedVersionOfEntityFramework ) ;
}
else
{
exceptionMessage . AppendLine ( Strings . Error_MetadataLoadError ) ;
exceptionMessage . AppendLine ( ) ;
exceptionMessage . Append ( ex . Message ) ;
}
ShowError ( exceptionMessage . ToString ( ) ) ;
}
}
return false ;
}
// Clears out the existing metadata
internal void ClearMetadata ( )
{
_entityConnection = null ;
}
/// <summary>
/// Finds and caches all non system assemblies that are found using the TypeDiscoveryService
/// This searches places like the ~/bin folder, app_code, and the 'assemblies' section of web.config, among others
/// </summary>
private void LoadAssemblies ( )
{
_assemblies = new HashSet < Assembly > ( ) ;
// Find the assemblies using the ITypeDiscoveryService
ITypeDiscoveryService typeDiscoverySvc = this . EntityDataSource . Site . GetService ( typeof ( ITypeDiscoveryService ) ) as ITypeDiscoveryService ;
if ( typeDiscoverySvc ! = null )
{
foreach ( Type type in typeDiscoverySvc . GetTypes ( typeof ( object ) , false /*excludeGlobalTypes*/ ) )
{
var assembly = type . Assembly ;
if ( ! _usingEntityFrameworkVersionHigherThanFive
& & assembly . GetName ( ) . Name . Equals ( "EntityFramework" , StringComparison . InvariantCultureIgnoreCase )
& & assembly . GetName ( ) . Version . Major > 5 )
{
_usingEntityFrameworkVersionHigherThanFive = true ;
ShowError ( Strings . Error_UnsupportedVersionOfEntityFramework ) ;
}
if ( ! _assemblies . Contains ( assembly ) & & ! IsSystemAssembly ( assembly . FullName ) )
{
_assemblies . Add ( assembly ) ;
}
}
}
}
// Explicitly rebuild the known assembly cache. This is done when launching the wizard and allows the wizard to pick up the latest
// assemblies in the project, without having to reload them everytime the connection string changes while the wizard is running
internal void ReloadResources ( )
{
Debug . Assert ( _interactiveMode = = true , "resource cache should only explicitly be loaded in interactive mode" ) ;
LoadAssemblies ( ) ;
}
// Is the assembly and its referenced assemblies not expected to have any metadata
// This does not detect all possible system assemblies, but just those we can detect as system for sure
private static bool IsSystemAssembly ( string fullName )
{
return ( String . Equals ( fullName , "*" , StringComparison . OrdinalIgnoreCase ) | |
fullName . EndsWith ( s_ecmPublicKeyToken , StringComparison . OrdinalIgnoreCase ) ) ;
}
// Combines all warnings into one message
private string BuildWarningMessage ( string headerMessage , List < string > warnings )
{
Debug . Assert ( warnings ! = null & & warnings . Count > 0 , "expected non-null and non-empty warnings" ) ;
StringBuilder warningMessage = new StringBuilder ( ) ;
warningMessage . AppendLine ( headerMessage ) ;
warningMessage . AppendLine ( ) ;
foreach ( string warning in warnings )
{
warningMessage . AppendLine ( warning ) ;
}
return warningMessage . ToString ( ) ;
}
// Get a connection string builder for the specified connection string, and do some basic verification
// namedConnStrBuilder should be based on a named connection and should already have been verified to be structurally valid
private EntityConnectionStringBuilder GetBuilderForNamedConnection ( EntityConnectionStringBuilder namedConnStrBuilder )
{
Debug . Assert ( namedConnStrBuilder ! = null & & ! String . IsNullOrEmpty ( namedConnStrBuilder . Name ) , "expected non-null connStrBuilder for a named connection" ) ;
// Need to get the actual string from the web.config
EntityConnectionStringBuilder connStrBuilder = null ;
if ( CanLoadWebConfig )
{
try
{
System . Configuration . Configuration webConfig = _webApplication . OpenWebConfiguration ( true /*isReadOnly*/ ) ;
if ( webConfig ! = null )
{
ConnectionStringSettings connStrSettings = webConfig . ConnectionStrings . ConnectionStrings [ namedConnStrBuilder . Name ] ;
if ( connStrSettings ! = null & & ! String . IsNullOrEmpty ( connStrSettings . ConnectionString ) & & connStrSettings . ProviderName = = s_entityClientProviderName )
{
// Verify the contents of the named connection and create a new builder from it
// It can't reference another named connection, and must have both the provider and metadata keywords
connStrBuilder = VerifyConnectionString ( connStrSettings . ConnectionString , false /*allowNamedConnections*/ ) ;
}
else
{
ShowError ( Strings . Error_NamedConnectionNotFound ) ;
}
}
else
{
ShowError ( Strings . Error_CannotOpenWebConfig_SpecificConnection ) ;
}
}
catch ( ConfigurationException ce )
{
StringBuilder error = new StringBuilder ( ) ;
error . AppendLine ( Strings . Error_CannotOpenWebConfig_SpecificConnection ) ;
error . AppendLine ( ) ;
error . AppendLine ( ce . Message ) ;
ShowError ( error . ToString ( ) ) ;
}
}
// could be null if verification failed
return connStrBuilder ;
}
// Make sure we have at least some basic keywords.This method does not attempt to do as much verification as EntityClient would do.
// We are just looking for a named connection, or both the provider and metadata keywords.
/// <summary>
/// Make sure we have at least some basic keywords.This method does not attempt to do as much verification as EntityClient would do.
/// We are just looking for a named connection, or both the provider and metadata keywords. ///
/// </summary>
/// <param name="connectionString">Connection string to be verified. Can be empty or null.</param>
/// <param name="allowNamedConnections">
/// Indicates if the specified string can be a named connection in the form "name=ConnectionName".
/// If this method is being called with a connection string that came from a named connection entry in the web.config, this should be false
/// because we do not support nested named connections.
/// </param>
/// <returns>
/// A new EntityConnectionStringBuilder if the basic verification succeeded, otherwise null. Can return a builder for an empty string.
/// </returns>
private EntityConnectionStringBuilder VerifyConnectionString ( string connectionString , bool allowNamedConnections )
{
// Verify if we have a structurally valid connection string with both "provider" and "metadata" keywords
EntityConnectionStringBuilder connStrBuilder = null ;
try
{
// Verify that it can be loaded into the builder to ensure basic valid structure and keywords
connStrBuilder = new EntityConnectionStringBuilder ( connectionString ) ;
}
catch ( ArgumentException ex )
{
// The message thrown from the connection string builder is not always useful to the user in this context, so add our own error text as well
ShowError ( Strings . Error_CreatingConnectionStringBuilder ( ex . Message ) ) ;
return null ;
}
Debug . Assert ( connStrBuilder ! = null , "expected non-null connStrBuilder" ) ;
// If the connection string is not empty, do some validation on it
// (a) If this is not supposed to be a named connection, make sure it isn't
// (b) Then it's not a named connection, then verify the keywords
// (c) Otherwise the connection string is a named connection, no further validation is needed
// devnote: Using the ConnectionString property on the builder in the check for empty, because the original connection string
// could have been something like "name=", which produces an empty ConnectionString in the builder, although the original was not empty
if ( ! String . IsNullOrEmpty ( connStrBuilder . ConnectionString ) )
{
// If named connection is not allowed, make sure it is not specified
if ( ! allowNamedConnections & & ! String . IsNullOrEmpty ( connStrBuilder . Name ) )
{
ShowError ( Strings . Error_NestedNamedConnection ) ;
return null ;
}
// If the connection string is not a named connection, verify the keywords
if ( String . IsNullOrEmpty ( connStrBuilder . Name ) )
{
if ( String . IsNullOrEmpty ( connStrBuilder . Metadata ) )
{
ShowError ( Strings . Error_MissingMetadataKeyword ) ;
return null ;
}
}
// else it's a named connection and we don't need to validate it further
}
return connStrBuilder ;
}
internal void ShowError ( string message )
{
if ( _interactiveMode )
{
UIHelper . ShowError ( EntityDataSource . Site , message ) ;
}
// else we are in a mode where we just want to ignore errors (typically this happens when called from the property grid)
}
internal void ShowWarning ( string message )
{
if ( _interactiveMode )
{
UIHelper . ShowWarning ( EntityDataSource . Site , message ) ;
}
// else we are in a mode where we just want to ignore warnings (typically this happens when called from the property grid)
}
// Removes any paths containing |DataDirectory| from a string of metadata locations, adds them to a separate list and expands
// the macro to the full path to ~/ for any paths that start with the macro
private string ResolveDataDirectory ( string metadataPaths , List < string > dataDirectoryPaths , List < string > warnings )
{
Debug . Assert ( dataDirectoryPaths ! = null , "null dataDirectoryPaths" ) ;
// If the argument contains one or more occurrences of the macro '|DataDirectory|', we
// pull those paths out so that we don't lose them in the string-splitting logic below.
// Note that the macro '|DataDirectory|' cannot have any whitespace between the pipe
// symbols and the macro name. Also note that the macro must appear at the beginning of
// a path (else we will eventually fail with an invalid path exception, because in that
// case the macro is not expanded). If a real/physical folder named 'DataDirectory' needs
// to be included in the metadata path, whitespace should be used on either or both sides
// of the name.
//
int indexStart = metadataPaths . IndexOf ( s_dataDirectory , StringComparison . OrdinalIgnoreCase ) ;
while ( indexStart ! = - 1 )
{
int prevSeparatorIndex = indexStart = = 0 ? - 1 : metadataPaths . LastIndexOf (
s_metadataPathSeparator ,
indexStart - 1 , // start looking here
StringComparison . Ordinal
) ;
int macroPathBeginIndex = prevSeparatorIndex + 1 ;
// The '|DataDirectory|' macro is composable, so identify the complete path, like
// '|DataDirectory|\item1\item2'. If the macro appears anywhere other than at the
// beginning, splice out the entire path, e.g. 'C:\item1\|DataDirectory|\item2'. In this
// latter case the macro will not be expanded, and downstream code will throw an exception.
//
int indexEnd = metadataPaths . IndexOf ( s_metadataPathSeparator ,
indexStart + s_dataDirectory . Length ,
StringComparison . Ordinal ) ;
string resolvedPath ;
if ( indexEnd = = - 1 )
{
resolvedPath = ExpandDataDirectory ( metadataPaths . Substring ( macroPathBeginIndex ) , warnings ) ;
if ( resolvedPath ! = null )
{
// only add to the list if no warning occurred
dataDirectoryPaths . Add ( resolvedPath ) ;
}
metadataPaths = metadataPaths . Remove ( macroPathBeginIndex ) ; // update the concatenated list of paths
break ;
}
resolvedPath = ExpandDataDirectory ( metadataPaths . Substring ( macroPathBeginIndex , indexEnd - macroPathBeginIndex ) , warnings ) ;
if ( resolvedPath ! = null )
{
// only add to the list if no warning occurred
dataDirectoryPaths . Add ( resolvedPath ) ;
}
// Update the concatenated list of paths by removing the one containing the macro.
//
metadataPaths = metadataPaths . Remove ( macroPathBeginIndex , indexEnd - macroPathBeginIndex ) ;
indexStart = metadataPaths . IndexOf ( s_dataDirectory , StringComparison . OrdinalIgnoreCase ) ;
}
return metadataPaths ;
}
// If the specified string starts with |DataDirectory|, replace that macro with the full path for ~/app_data in the application
private string ExpandDataDirectory ( string pathWithMacro , List < string > warnings )
{
string trimmedPath = pathWithMacro . Trim ( ) ;
if ( trimmedPath . StartsWith ( s_dataDirectory , StringComparison . OrdinalIgnoreCase ) )
{
string dataDirectoryPath = GetDataDirectory ( ) ;
if ( dataDirectoryPath ! = null )
{
return String . Concat ( dataDirectoryPath , trimmedPath . Substring ( s_dataDirectory . Length ) ) ;
}
else
{
warnings . Add ( Strings . Warning_DataDirectoryNotFound ( trimmedPath ) ) ;
return null ;
}
}
// else the macro is somewhere in the middle of the string which is not valid anyway, so just pass it along and let the metadata failure occur
return trimmedPath ;
}
/// <summary>
/// Resolves the |DataDirecotry| macro from the current web application
/// </summary>
/// <returns>The physical path for the macro expansion, or null if the data directory could not be found</returns>
private string GetDataDirectory ( )
{
IProjectItem dataDirectoryPath = _webApplication . GetProjectItemFromUrl ( s_dataDirectoryPath ) ;
if ( dataDirectoryPath ! = null )
{
return dataDirectoryPath . PhysicalPath ;
}
else
{
return null ;
}
}
private void ResolveVirtualRootPath ( string resourcePath , List < string > metadataPaths , List < string > warnings )
{
IProjectItem rootItem = _webApplication . GetProjectItemFromUrl ( s_virtualRoot ) ;
if ( rootItem ! = null )
{
metadataPaths . Add ( String . Concat ( rootItem . PhysicalPath , resourcePath . Substring ( s_virtualRoot . Length ) ) ) ;
}
else
{
warnings . Add ( Strings . Warning_VirtualRootNotFound ( resourcePath ) ) ;
}
}
private void ResolveRelativePath ( string resourcePath , List < string > metadataPaths , List < string > warnings )
{
IProjectItem rootItem = _webApplication . GetProjectItemFromUrl ( s_virtualRoot ) ;
if ( rootItem ! = null )
{
metadataPaths . Add ( String . Concat ( rootItem . PhysicalPath , resourcePath ) ) ;
}
else
{
warnings . Add ( Strings . Warning_VirtualRootNotFound ( resourcePath ) ) ;
}
}
// Create a DbConnection for the specified connection string
private DbConnection GetDbConnection ( EntityConnectionStringBuilder connStrBuilder )
{
DbProviderFactory factory = null ;
if ( ! string . IsNullOrEmpty ( connStrBuilder . Provider ) )
{
try
{
// Get the correct provider factory
factory = DbProviderFactories . GetFactory ( connStrBuilder . Provider ) ;
}
catch ( Exception ex )
{
ShowError ( Strings . Error_CannotCreateDbProviderFactory ( ex . Message ) ) ;
}
}
else
{
ShowError ( Strings . Error_CannotCreateDbProviderFactory ( Strings . Error_MissingProviderKeyword ) ) ;
}
if ( factory ! = null )
{
try
{
// Create the underlying provider specific connection and give it the specified provider connection string
DbConnection storeConnection = factory . CreateConnection ( ) ;
if ( storeConnection ! = null )
{
storeConnection . ConnectionString = connStrBuilder . ProviderConnectionString ;
return storeConnection ;
}
}
catch ( Exception )
{
// eat any exceptions and just show the general error below
}
ShowError ( Strings . Error_ReturnedNullOnProviderMethod ( factory . GetType ( ) . Name ) ) ;
}
return null ;
}
internal void RefreshSchema ( bool preferSilent )
{
string originalDataDirectory = null ;
try
{
_owner . SuppressDataSourceEvents ( ) ;
Cursor originalCursor = Cursor . Current ;
// Make sure we have set the |DataDirectory| field in the AppDomain so that the underlying providers
// can make use of this macro
originalDataDirectory = AppDomain . CurrentDomain . GetData ( s_dataDirectoryNoPipes ) as string ;
AppDomain . CurrentDomain . SetData ( s_dataDirectoryNoPipes , GetDataDirectory ( ) ) ;
// Verify that we can get the current schema
DataTable currentSchema = GetCurrentSchema ( preferSilent ) ;
if ( currentSchema = = null )
{
// error occurred when getting current schema
return ;
}
try
{
Cursor . Current = Cursors . WaitCursor ;
EntityDesignerDataSourceView view = GetView ( DefaultViewName ) ;
IDataSourceViewSchema oldViewSchema = view . Schema ;
bool wasForceUsed = false ;
if ( oldViewSchema = = null )
{
ForceSchemaRetrieval = true ;
oldViewSchema = view . Schema ;
ForceSchemaRetrieval = false ;
wasForceUsed = true ;
}
SaveSchema ( this . ConnectionString , this . DefaultContainerName , this . EntitySetName , this . Select , this . CommandText , this . EnableFlattening , currentSchema ) ;
// Compare new schema to old schema and if it changed, raise the SchemaRefreshed event
bool viewSchemaEquivalent = _owner . InternalViewSchemasEquivalent ( oldViewSchema , view . Schema ) ;
if ( ! viewSchemaEquivalent )
{
_owner . FireOnSchemaRefreshed ( EventArgs . Empty ) ;
}
else if ( wasForceUsed )
{
// if the schemas were equivalent but the schema retrieval was forced, still raise the data source changed event
_owner . FireOnDataSourceChanged ( EventArgs . Empty ) ;
}
}
finally
{
Cursor . Current = originalCursor ;
}
}
finally
{
// Reset the AppDomain to its original |DataDirectory| value
AppDomain . CurrentDomain . SetData ( s_dataDirectoryNoPipes , originalDataDirectory ) ;
_owner . ResumeDataSourceEvents ( ) ;
}
}
private DataTable GetCurrentSchema ( bool preferSilent )
{
// Verify that we have values for a minimum set of properties that will be required to get schema
if ( String . IsNullOrEmpty ( this . EntityDataSource . ConnectionString ) | |
String . IsNullOrEmpty ( this . EntityDataSource . DefaultContainerName ) | |
String . IsNullOrEmpty ( this . EntityDataSource . CommandText ) & & String . IsNullOrEmpty ( this . EntityDataSource . EntitySetName ) )
{
if ( ! preferSilent )
{
ShowError ( Strings . Error_CannotRefreshSchema_MissingProperties ) ;
}
return null ;
}
bool originalMode = _interactiveMode ;
try
{
// Suppress error messages while loading metadata if we are in silent mode
_interactiveMode = ! preferSilent ;
// In interactive mode, always clear any cached information so we are sure to get the latest schema
// This is necessary in case the metadata or entity classes in referenced assemblies has changed
// or in case the metadata files have changed without changing any of the properties on the control
if ( _interactiveMode )
{
ReloadResources ( ) ;
ClearMetadata ( ) ;
}
// Try to load metadata if we don't have it yet
if ( _entityConnection = = null )
{
if ( ! LoadMetadata ( ) )
{
return null ;
}
// else metadata was successfully loaded, so continue refreshing schema
}
}
finally
{
_interactiveMode = originalMode ;
}
// Either _entityConnection was already set, or we should have successfully loaded it
Debug . Assert ( _entityConnection ! = null , "_entityConnection should have been initialized" ) ;
try
{
// Create a temporary data source based on the EntityConnection we have built with
// the right metadata from the design-time environment
EntityDataSource entityDataSource = new EntityDataSource ( _entityConnection ) ;
// This is workaround for a bug in the SQL CE provider services. SQL CE uses two providers - one is supposed to be used at design time
// while the other one is supposed to be used at runtime. When the Entiy Designer is used in a way that requires to talk to the database
// SQL CE starts returning design time provider. However they don't reset an internal flag and continue to return design time provider even if
// the Entity Designer is not used anymore. Calling GetProviderManifestToken() method will reset the flag according to the provider in the
// connection. This fixes the problem for SQL CE provider without having to special case SQL CE because it will be a no-op for other providers.
// For more details see bug 35675 in DevDiv database http://vstfdevdiv:8080/web/wi.aspx?pcguid=22f9acc9-569a-41ff-b6ac-fac1b6370209&id=35675
DbProviderServices . GetProviderServices ( _entityConnection . StoreConnection ) . GetProviderManifestToken ( _entityConnection . StoreConnection ) ;
// Copy only the properties that can affect the schema
entityDataSource . CommandText = this . EntityDataSource . CommandText ;
CopyParameters ( this . EntityDataSource . CommandParameters , entityDataSource . CommandParameters ) ;
entityDataSource . DefaultContainerName = this . EntityDataSource . DefaultContainerName ;
entityDataSource . EntitySetName = this . EntityDataSource . EntitySetName ;
entityDataSource . EntityTypeFilter = this . EntityDataSource . EntityTypeFilter ;
entityDataSource . GroupBy = this . EntityDataSource . GroupBy ;
entityDataSource . Select = this . EntityDataSource . Select ;
entityDataSource . EnableFlattening = this . EntityDataSource . EnableFlattening ;
CopyParameters ( this . EntityDataSource . SelectParameters , entityDataSource . SelectParameters ) ;
EntityDataSourceView view = ( EntityDataSourceView ) ( ( ( IDataSource ) entityDataSource ) . GetView ( DefaultViewName ) ) ;
DataTable viewTable = view . GetViewSchema ( ) ;
viewTable . TableName = DefaultViewName ;
return viewTable ;
}
catch ( Exception ex )
{
if ( ! preferSilent )
{
StringBuilder errorMessage = new StringBuilder ( ) ;
errorMessage . AppendLine ( Strings . Error_CannotRefreshSchema_RuntimeException ( ex . Message ) ) ;
if ( ex . InnerException ! = null )
{
errorMessage . AppendLine ( Strings . Error_CannotRefreshSchema_RuntimeException_InnerException ( ex . InnerException . Message ) ) ;
}
ShowError ( errorMessage . ToString ( ) ) ;
}
}
return null ;
}
private void CopyParameters ( ParameterCollection originalParameters , ParameterCollection newParameters )
{
Debug . Assert ( originalParameters ! = null & & newParameters ! = null , "parameter collections on the data source should never be null" ) ;
Debug . Assert ( newParameters . Count = = 0 , "new parameter collection should not contain any parameters yet" ) ;
_owner . CloneParameters ( originalParameters , newParameters ) ;
}
// Loads the schema
internal DataTable LoadSchema ( )
{
if ( ! ForceSchemaRetrieval )
{
// Only check for consistency if we are not forcing the retrieval
string connectionString = _owner . LoadFromDesignerState ( DesignerStateDataSourceConnectionStringKey ) as string ;
string defaultContainerName = _owner . LoadFromDesignerState ( DesignerStateDataSourceDefaultContainerNameKey ) as string ;
string entitySetName = _owner . LoadFromDesignerState ( DesignerStateDataSourceEntitySetNameKey ) as string ;
string select = _owner . LoadFromDesignerState ( DesignerStateDataSourceSelectKey ) as string ;
string commandText = _owner . LoadFromDesignerState ( DesignerStateDataSourceCommandTextKey ) as string ;
object enableFlattening = _owner . LoadFromDesignerState ( DesignerStateDataSourceEnableFlatteningKey ) ;
if ( ! String . Equals ( connectionString , this . ConnectionString , StringComparison . OrdinalIgnoreCase ) | |
! String . Equals ( defaultContainerName , this . DefaultContainerName , StringComparison . OrdinalIgnoreCase ) | |
! String . Equals ( entitySetName , this . EntitySetName , StringComparison . OrdinalIgnoreCase ) | |
! String . Equals ( select , this . Select , StringComparison . OrdinalIgnoreCase ) | |
! String . Equals ( commandText , this . CommandText , StringComparison . OrdinalIgnoreCase ) | |
( enableFlattening = = null | | ( ( ( bool ) enableFlattening ) ! = this . EnableFlattening ) ) )
{
return null ;
}
}
// Either we are forcing schema retrieval, or we're not forcing but we're consistent, so get the schema
DataTable schema = _owner . LoadFromDesignerState ( DesignerStateDataSourceSchemaKey ) as DataTable ;
return schema ;
}
private void SaveSchema ( string connectionString , string defaultContainerName , string entitySetName ,
string select , string commandText , bool enableFlattening , DataTable currentSchema )
{
// Save the schema to DesignerState
_owner . SaveDesignerState ( DesignerStateDataSourceConnectionStringKey , connectionString ) ;
_owner . SaveDesignerState ( DesignerStateDataSourceDefaultContainerNameKey , defaultContainerName ) ;
_owner . SaveDesignerState ( DesignerStateDataSourceEntitySetNameKey , entitySetName ) ;
_owner . SaveDesignerState ( DesignerStateDataSourceSelectKey , select ) ;
_owner . SaveDesignerState ( DesignerStateDataSourceCommandTextKey , commandText ) ;
_owner . SaveDesignerState ( DesignerStateDataSourceEnableFlatteningKey , enableFlattening ) ;
_owner . SaveDesignerState ( DesignerStateDataSourceSchemaKey , currentSchema ) ;
}
// Gets a view (can only get the default view)
internal EntityDesignerDataSourceView GetView ( string viewName )
{
if ( String . IsNullOrEmpty ( viewName ) | |
String . Equals ( viewName , DefaultViewName , StringComparison . OrdinalIgnoreCase ) )
{
if ( View = = null )
{
View = new EntityDesignerDataSourceView ( _owner ) ;
}
return View ;
}
return null ;
}
// Gets a list of view names
internal string [ ] GetViewNames ( )
{
return new string [ ] { DefaultViewName } ;
}
// Caller can specify that the results should not be sorted if they may add something to the list and sort themselves
internal List < EntityDataSourceContainerNameItem > GetContainerNames ( bool sortResults )
{
List < EntityDataSourceContainerNameItem > entityContainerItems = new List < EntityDataSourceContainerNameItem > ( ) ;
if ( this . EdmItemCollection ! = null )
{
ReadOnlyCollection < EntityContainer > entityContainers = this . EdmItemCollection . GetItems < EntityContainer > ( ) ;
foreach ( EntityContainer entityContainer in entityContainers )
{
entityContainerItems . Add ( new EntityDataSourceContainerNameItem ( entityContainer ) ) ;
}
if ( sortResults )
{
entityContainerItems . Sort ( ) ;
}
}
return entityContainerItems ;
}
internal EntityDataSourceContainerNameItem GetEntityContainerItem ( string entityContainerName )
{
if ( String . IsNullOrEmpty ( entityContainerName ) )
{
return null ; // can't make a valid wrapper with an empty container name
}
EntityContainer container = null ;
if ( this . EdmItemCollection ! = null & &
this . EdmItemCollection . TryGetEntityContainer ( entityContainerName , true /*ignoreCase*/ , out container ) & &
container ! = null )
{
return new EntityDataSourceContainerNameItem ( container ) ;
}
else
{
return new EntityDataSourceContainerNameItem ( entityContainerName ) ;
}
}
internal List < EntityDataSourceEntitySetNameItem > GetEntitySets ( string entityContainerName )
{
EntityContainer container = null ;
if ( this . EdmItemCollection ! = null )
{
this . EdmItemCollection . TryGetEntityContainer ( entityContainerName , true /*ignoreCase*/ , out container ) ;
}
return GetEntitySets ( container , true /*sortResults*/ ) ;
}
// Caller can specify that the results should not be sorted if they may add something to the list and sort themselves
internal List < EntityDataSourceEntitySetNameItem > GetEntitySets ( EntityContainer entityContainer , bool sortResults )
{
List < EntityDataSourceEntitySetNameItem > entitySetNameItems = new List < EntityDataSourceEntitySetNameItem > ( ) ;
if ( entityContainer ! = null )
{
foreach ( EntitySetBase entitySetBase in entityContainer . BaseEntitySets )
{
// BaseEntitySets returns RelationshipSets too, but we only want EntitySets
if ( entitySetBase . BuiltInTypeKind = = BuiltInTypeKind . EntitySet )
{
entitySetNameItems . Add ( new EntityDataSourceEntitySetNameItem ( entitySetBase as EntitySet ) ) ;
}
}
if ( sortResults )
{
entitySetNameItems . Sort ( ) ;
}
}
return entitySetNameItems ;
}
// Caller can specify that the results should not be sorted if they may add something to the list and sort themselves
internal List < EntityConnectionStringBuilderItem > GetNamedEntityClientConnections ( bool sortResults )
{
List < EntityConnectionStringBuilderItem > namedEntityClientConnections = new List < EntityConnectionStringBuilderItem > ( ) ;
System . Configuration . Configuration webConfig = _webApplication . OpenWebConfiguration ( true /*isReadOnly*/ ) ;
if ( webConfig ! = null )
{
try
{
foreach ( ConnectionStringSettings connStrSettings in webConfig . ConnectionStrings . ConnectionStrings )
{
if ( connStrSettings . ProviderName = = s_entityClientProviderName )
{
EntityConnectionStringBuilder connStrBuilder = new EntityConnectionStringBuilder ( ) ;
connStrBuilder . Name = connStrSettings . Name ;
namedEntityClientConnections . Add ( new EntityConnectionStringBuilderItem ( connStrBuilder ) ) ;
}
}
if ( sortResults )
{
namedEntityClientConnections . Sort ( ) ;
}
}
catch ( ConfigurationException ce )
{
CanLoadWebConfig = false ;
namedEntityClientConnections . Clear ( ) ;
StringBuilder error = new StringBuilder ( ) ;
error . AppendLine ( Strings . Warning_CannotOpenWebConfig_AllConnections ) ;
error . AppendLine ( ) ;
error . AppendLine ( ce . Message ) ;
ShowWarning ( error . ToString ( ) ) ;
}
}
else
{
ShowWarning ( Strings . Warning_CannotOpenWebConfig_AllConnections ) ;
}
return namedEntityClientConnections ;
}
internal EntityConnectionStringBuilderItem GetEntityConnectionStringBuilderItem ( string connectionString )
{
EntityConnectionStringBuilder connStrBuilder = VerifyConnectionString ( connectionString , true /*allowNamedConnections*/ ) ;
if ( connStrBuilder ! = null )
{
return new EntityConnectionStringBuilderItem ( connStrBuilder ) ;
}
else
{
return new EntityConnectionStringBuilderItem ( connectionString ) ;
}
}
internal List < string > GetEntityTypeProperties ( EntityType entityType )
{
List < string > properties = new List < string > ( ) ;
foreach ( EdmProperty property in entityType . Properties )
{
properties . Add ( property . Name ) ;
}
Debug . Assert ( properties . Count > 0 , "expected entity to have at least one property" ) ;
// don't sort the properties here because it will cause them to be displayed to the user in a non-intuitive order
return properties ;
}
internal List < EntityDataSourceEntityTypeFilterItem > GetEntityTypeFilters ( string entityContainerName , string entitySetName )
{
EntityType baseEntitySetType = null ;
if ( this . EdmItemCollection ! = null )
{
EntityContainer container ;
if ( this . EdmItemCollection . TryGetEntityContainer ( entityContainerName , true /*ignoreCase*/ , out container ) & & ( container ! = null ) )
{
EntitySet entitySet ;
if ( container . TryGetEntitySetByName ( entitySetName , true /*ignoreCase*/ , out entitySet ) & & entitySet ! = null )
{
baseEntitySetType = entitySet . ElementType ;
}
}
}
return GetEntityTypeFilters ( baseEntitySetType , true /*sortResults*/ ) ;
}
internal List < EntityDataSourceEntityTypeFilterItem > GetEntityTypeFilters ( EntityType baseEntitySetType , bool sortResults )
{
List < EntityDataSourceEntityTypeFilterItem > derivedTypes = new List < EntityDataSourceEntityTypeFilterItem > ( ) ;
if ( baseEntitySetType ! = null & & this . EdmItemCollection ! = null )
{
foreach ( EntityType entityType in GetTypeAndSubtypesOf ( baseEntitySetType , this . EdmItemCollection ) )
{
derivedTypes . Add ( new EntityDataSourceEntityTypeFilterItem ( entityType ) ) ;
}
if ( sortResults )
{
derivedTypes . Sort ( ) ;
}
}
return derivedTypes ;
}
#region Helper methods for finding possible types for EntityTypeFilter
private static IEnumerable < EntityType > GetTypeAndSubtypesOf ( EntityType entityType , EdmItemCollection itemCollection )
{
// Always include the specified type, even if it's abstract
yield return entityType ;
// Get the subtypes of the type from the item collection
IEnumerable < EntityType > entityTypesInCollection = itemCollection . OfType < EntityType > ( ) ;
foreach ( EntityType typeInCollection in entityTypesInCollection )
{
if ( entityType . Equals ( typeInCollection ) = = false & & IsStrictSubtypeOf ( typeInCollection , entityType ) )
{
yield return typeInCollection ;
}
}
yield break ;
}
// requires: firstType is not null
// effects: if otherType is among the base types, return true,
// otherwise returns false.
// when othertype is same as the current type, return false.
private static bool IsStrictSubtypeOf ( EntityType firstType , EntityType secondType )
{
Debug . Assert ( firstType ! = null , "firstType should not be not null" ) ;
if ( secondType = = null )
{
return false ;
}
// walk up my type hierarchy list
for ( EntityType t = ( EntityType ) firstType . BaseType ; t ! = null ; t = ( EntityType ) t . BaseType )
{
if ( t = = secondType )
return true ;
}
return false ;
}
#endregion
// Copy properties from temporary state to the data source
internal void SaveEntityDataSourceProperties ( EntityDataSourceState state )
{
this . EntityDataSource . ConnectionString = state . ConnectionString ;
this . EntityDataSource . DefaultContainerName = state . DefaultContainerName ;
this . EntityDataSource . EnableDelete = state . EnableDelete ;
this . EntityDataSource . EnableInsert = state . EnableInsert ;
this . EntityDataSource . EnableUpdate = state . EnableUpdate ;
this . EntityDataSource . EntitySetName = state . EntitySetName ;
this . EntityDataSource . EntityTypeFilter = state . EntityTypeFilter ;
this . EntityDataSource . Select = state . Select ;
this . EntityDataSource . EnableFlattening = state . EnableFlattening ;
}
// Copy properties from the data source to temporary state
internal EntityDataSourceState LoadEntityDataSourceState ( )
{
EntityDataSourceState state = new EntityDataSourceState ( ) ;
state . ConnectionString = this . EntityDataSource . ConnectionString ;
state . DefaultContainerName = this . EntityDataSource . DefaultContainerName ;
state . EnableDelete = this . EntityDataSource . EnableDelete ;
state . EnableInsert = this . EntityDataSource . EnableInsert ;
state . EnableUpdate = this . EntityDataSource . EnableUpdate ;
state . EntitySetName = this . EntityDataSource . EntitySetName ;
state . EntityTypeFilter = this . EntityDataSource . EntityTypeFilter ;
state . Select = this . EntityDataSource . Select ;
return state ;
}
}
}