e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
1500 lines
66 KiB
C#
1500 lines
66 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="EntityDataSourceDesignerHelper.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner [....]
|
|
// @backupOwner [....]
|
|
//------------------------------------------------------------------------------
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|