1 //------------------------------------------------------------------------------
2 // <copyright file="SqlProviderServices.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //------------------------------------------------------------------------------
10 namespace System.Data.SqlClient {
12 using System.Collections.Generic;
13 using System.Data.Common;
14 using System.Data.Common.CommandTrees;
15 using System.Data.Common.Utils;
16 using System.Data.Entity;
17 using System.Data.Metadata.Edm;
18 using System.Data.Spatial;
19 using System.Diagnostics;
21 using System.Globalization;
24 /// The DbProviderServices implementation for the SqlClient provider for SQL Server.
27 public sealed class SqlProviderServices : DbProviderServices
30 /// Private constructor to ensure only Singleton instance is created.
32 private SqlProviderServices()
39 internal static readonly SqlProviderServices Instance = new SqlProviderServices();
42 /// The Singleton instance of the SqlProviderServices type.
44 public static SqlProviderServices SingletonInstance
46 get { return Instance; }
50 /// Create a Command Definition object, given the connection and command tree
52 /// <param name="providerManifest">provider manifest that was determined from metadata</param>
53 /// <param name="commandTree">command tree for the statement</param>
54 /// <returns>an exectable command definition object</returns>
55 protected override DbCommandDefinition CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree) {
56 Debug.Assert(providerManifest != null, "CreateCommandDefinition passed null provider manifest to CreateDbCommandDefinition?");
57 Debug.Assert(commandTree != null, "CreateCommandDefinition did not validate commandTree argument?");
59 DbCommand prototype = CreateCommand(providerManifest, commandTree);
60 DbCommandDefinition result = this.CreateCommandDefinition(prototype);
65 /// Create a SqlCommand object given a command tree
67 /// <param name="commandTree">command tree for the statement</param>
68 /// <returns>a command object</returns>
69 internal override DbCommand CreateCommand(DbCommandTree commandTree) {
70 EntityUtil.CheckArgumentNull(commandTree, "commandTree");
71 StoreItemCollection storeMetadata = (StoreItemCollection)commandTree.MetadataWorkspace.GetItemCollection(DataSpace.SSpace);
72 Debug.Assert(storeMetadata.StoreProviderManifest != null, "StoreItemCollection has null StoreProviderManifest?");
74 return this.CreateCommand(storeMetadata.StoreProviderManifest, commandTree);
78 /// Create a SqlCommand object, given the provider manifest and command tree
80 /// <param name="providerManifest">provider manifest</param>
81 /// <param name="commandTree">command tree for the statement</param>
82 /// <returns>a command object</returns>
83 private DbCommand CreateCommand(DbProviderManifest providerManifest, DbCommandTree commandTree) {
84 EntityUtil.CheckArgumentNull(providerManifest, "providerManifest");
85 EntityUtil.CheckArgumentNull(commandTree, "commandTree");
87 SqlProviderManifest sqlManifest = (providerManifest as SqlProviderManifest);
88 if (sqlManifest == null)
90 throw EntityUtil.Argument(System.Data.Entity.Strings.Mapping_Provider_WrongManifestType(typeof(SqlProviderManifest)));
93 SqlVersion sqlVersion = sqlManifest.SqlVersion;
94 SqlCommand command = new SqlCommand();
96 List<SqlParameter> parameters;
97 CommandType commandType;
98 HashSet<string> paramsToForceNonUnicode;
99 command.CommandText = System.Data.SqlClient.SqlGen.SqlGenerator.GenerateSql(commandTree, sqlVersion, out parameters, out commandType, out paramsToForceNonUnicode);
100 command.CommandType = commandType;
102 // Get the function (if any) implemented by the command tree since this influences our interpretation of parameters
103 EdmFunction function = null;
104 if (commandTree.CommandTreeKind == DbCommandTreeKind.Function) {
105 function = ((DbFunctionCommandTree)commandTree).EdmFunction;
107 // Now make sure we populate the command's parameters from the CQT's parameters:
108 foreach (KeyValuePair<string, TypeUsage> queryParameter in commandTree.Parameters) {
109 SqlParameter parameter;
111 // Use the corresponding function parameter TypeUsage where available (currently, the SSDL facets and
112 // type trump user-defined facets and type in the EntityCommand).
113 FunctionParameter functionParameter;
114 if (null != function && function.Parameters.TryGetValue(queryParameter.Key, false, out functionParameter)) {
115 const bool preventTruncation = false;
116 parameter = CreateSqlParameter(functionParameter.Name, functionParameter.TypeUsage, functionParameter.Mode, DBNull.Value, preventTruncation, sqlVersion);
119 TypeUsage parameterType;
120 if ( (paramsToForceNonUnicode != null) && //Reached when a Function Command Tree is passed an incorrect parameter name by the user.
121 (paramsToForceNonUnicode.Contains(queryParameter.Key)) )
123 parameterType = queryParameter.Value.ShallowCopy(new FacetValues { Unicode = false });
127 parameterType = queryParameter.Value;
129 const bool preventTruncation = false;
130 parameter = CreateSqlParameter(queryParameter.Key, parameterType, ParameterMode.In, DBNull.Value, preventTruncation, sqlVersion);
132 command.Parameters.Add(parameter);
135 // Now add parameters added as part of SQL gen (note: this feature is only safe for DML SQL gen which
136 // does not support user parameters, where there is no risk of name collision)
137 if (null != parameters && 0 < parameters.Count) {
138 if (commandTree.CommandTreeKind != DbCommandTreeKind.Delete &&
139 commandTree.CommandTreeKind != DbCommandTreeKind.Insert &&
140 commandTree.CommandTreeKind != DbCommandTreeKind.Update) {
141 throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.SqlGenParametersNotPermitted);
143 foreach (SqlParameter parameter in parameters) {
144 command.Parameters.Add(parameter);
151 protected override void SetDbParameterValue(DbParameter parameter, TypeUsage parameterType, object value)
153 // Ensure a value that can be used with SqlParameter
154 value = EnsureSqlParameterValue(value);
156 if (TypeSemantics.IsPrimitiveType(parameterType, PrimitiveTypeKind.String) ||
157 TypeSemantics.IsPrimitiveType(parameterType, PrimitiveTypeKind.Binary))
159 int? size = GetParameterSize(parameterType, ((parameter.Direction & ParameterDirection.Output) == ParameterDirection.Output));
162 // Remember the current Size
163 int previousSize = parameter.Size;
165 // Infer the Size from the value
167 parameter.Value = value;
169 if (previousSize > -1)
171 // The 'max' length was chosen as a specific value for the parameter's Size property on Sql8 (4000 or 8000)
172 // because no MaxLength was specified in the TypeUsage and the provider is Sql8.
173 // If the value's length is less than or equal to this preset size, then the Size value can be retained,
174 // otherwise this preset size must be removed in favor of the Size inferred from the value itself.
176 // If the inferred Size is less than the preset 'max' size, restore that preset size
177 if (parameter.Size < previousSize)
179 parameter.Size = previousSize;
184 // -1 was chosen as the parameter's size because no MaxLength was specified in the TypeUsage and the
185 // provider is more recent than Sql8. However, it is more optimal to specify a non-max (-1) value for
186 // the size where possible, since 'max' parameters may prevent, for example, filter pushdown.
187 // (see Dev10#617447 for more details)
188 int suggestedLength = GetNonMaxLength(((SqlParameter)parameter).SqlDbType);
189 if (parameter.Size < suggestedLength)
191 parameter.Size = suggestedLength;
193 else if (parameter.Size > suggestedLength)
195 // The parameter size is greater than the suggested length, so the suggested length cannot be used.
196 // Since the provider is Sql9 or newer, set the size to max (-1) instead of the inferred size for better plan reuse.
203 // Just set the value
204 parameter.Value = value;
209 // Not a string or binary parameter - just set the value
210 parameter.Value = value;
214 protected override string GetDbProviderManifestToken(DbConnection connection) {
215 EntityUtil.CheckArgumentNull(connection, "connection");
217 SqlConnection sqlConnection = SqlProviderUtilities.GetRequiredSqlConnection(connection);
219 if (string.IsNullOrEmpty(sqlConnection.ConnectionString)) {
220 throw EntityUtil.Argument(Strings.UnableToDetermineStoreVersion);
223 string providerManifestToken = null;
224 // Try to get the provider manifest token from the database connection
225 // That failing, try using connection to master database (in case the database doesn't exist yet)
228 UsingConnection(sqlConnection, conn =>
230 providerManifestToken = SqlVersionUtils.GetVersionHint(SqlVersionUtils.GetSqlVersion(conn));
235 UsingMasterConnection(sqlConnection, conn =>
237 providerManifestToken = SqlVersionUtils.GetVersionHint(SqlVersionUtils.GetSqlVersion(conn));
240 return providerManifestToken;
243 protected override DbProviderManifest GetDbProviderManifest(string versionHint) {
244 if (string.IsNullOrEmpty(versionHint)) {
245 throw EntityUtil.Argument(Strings.UnableToDetermineStoreVersion);
248 return new SqlProviderManifest(versionHint);
251 protected override Spatial.DbSpatialDataReader GetDbSpatialDataReader(DbDataReader fromReader, string versionHint)
253 EntityUtil.CheckArgumentNull(fromReader, "fromReader");
255 ValidateVersionHint(versionHint);
257 SqlDataReader underlyingReader = fromReader as SqlDataReader;
258 if (underlyingReader == null)
260 throw EntityUtil.ProviderIncompatible(Strings.SqlProvider_NeedSqlDataReader(fromReader.GetType()));
264 return new SqlSpatialDataReader(underlyingReader);
267 protected override DbSpatialServices DbGetSpatialServices(string versionHint)
269 ValidateVersionHint(versionHint);
270 return SqlSpatialServices.Instance;
273 void ValidateVersionHint(string versionHint)
275 if (string.IsNullOrEmpty(versionHint))
277 throw EntityUtil.Argument(Strings.UnableToDetermineStoreVersion);
280 // GetSqlVersion will throw ArgumentException if manifestToken is null, empty, or not recognized.
281 SqlVersion tokenVersion = SqlVersionUtils.GetSqlVersion(versionHint);
283 // SQL spatial support is only available for SQL Server 2008 and later
284 if (tokenVersion < SqlVersion.Sql10)
286 throw EntityUtil.ProviderIncompatible(Strings.SqlProvider_Sql2008RequiredForSpatial);
290 internal static SqlTypesAssembly GetSqlTypesAssembly()
292 SqlTypesAssembly sqlTypes;
293 if (!TryGetSqlTypesAssembly(out sqlTypes))
295 throw EntityUtil.SqlTypesAssemblyNotFound();
297 Debug.Assert(sqlTypes != null);
301 internal static bool SqlTypesAssemblyIsAvailable
305 SqlTypesAssembly notUsed;
306 return TryGetSqlTypesAssembly(out notUsed);
310 private static bool TryGetSqlTypesAssembly(out SqlTypesAssembly sqlTypesAssembly)
312 sqlTypesAssembly = SqlTypesAssembly.Latest;
313 return sqlTypesAssembly != null;
317 /// Creates a SqlParameter given a name, type, and direction
319 internal static SqlParameter CreateSqlParameter(string name, TypeUsage type, ParameterMode mode, object value, bool preventTruncation, SqlVersion version) {
325 value = EnsureSqlParameterValue(value);
327 SqlParameter result = new SqlParameter(name, value);
330 ParameterDirection direction = MetadataHelper.ParameterModeToParameterDirection(mode);
331 if (result.Direction != direction) {
332 result.Direction = direction;
335 // .Size, .Precision, .Scale and .SqlDbType
336 // output parameters are handled differently (we need to ensure there is space for return
337 // values where the user has not given a specific Size/MaxLength)
338 bool isOutParam = mode != ParameterMode.In;
339 SqlDbType sqlDbType = GetSqlDbType(type, isOutParam, version, out size, out precision, out scale, out udtTypeName);
341 if (result.SqlDbType != sqlDbType) {
342 result.SqlDbType = sqlDbType;
345 if (sqlDbType == SqlDbType.Udt)
347 result.UdtTypeName = udtTypeName;
350 // Note that we overwrite 'facet' parameters where either the value is different or
351 // there is an output parameter. This is because output parameters in SqlClient have their
352 // facets clobbered if they are implicitly set (e.g. if the Size was implicitly set
353 // by setting the value)
356 // size.HasValue is always true for Output parameters
357 if ((isOutParam || result.Size != size.Value))
359 if (preventTruncation && size.Value != -1)
361 // To prevent truncation, set the Size of the parameter to the larger of either
362 // the declared length or the actual length for the parameter. This allows SQL
363 // Server to complain if a value is too long while preventing cache misses for
364 // values within the range.
365 result.Size = Math.Max(result.Size, size.Value);
369 result.Size = size.Value;
375 PrimitiveTypeKind typeKind = MetadataHelper.GetPrimitiveTypeKind(type);
376 if (typeKind == PrimitiveTypeKind.String)
378 result.Size = GetDefaultStringMaxLength(version, sqlDbType);
380 else if(typeKind == PrimitiveTypeKind.Binary)
382 result.Size = GetDefaultBinaryMaxLength(version);
385 if (precision.HasValue && (isOutParam || result.Precision != precision.Value)) {
386 result.Precision = precision.Value;
388 if (scale.HasValue && (isOutParam || result.Scale != scale.Value)) {
389 result.Scale = scale.Value;
393 bool isNullable = TypeSemantics.IsNullable(type);
394 if (isOutParam || isNullable != result.IsNullable) {
395 result.IsNullable = isNullable;
402 /// Validates that the specified value is compatible with SqlParameter and if not, attempts to return an appropriate value that is.
403 /// Currently only spatial values (DbGeography/DbGeometry) may not be directly usable with SqlParameter. For these types, an instance
404 /// of the corresponding SQL Server CLR spatial UDT will be manufactured based on the spatial data contained in <paramref name="value"/>.
405 /// If <paramref name="value"/> is an instance of DbGeography/DbGeometry that was read from SQL Server by this provider, then the wrapped
406 /// CLR UDT value is available via the ProviderValue property (see SqlSpatialServices for the full conversion process from instances of
407 /// DbGeography/DbGeometry to instances of the CLR SqlGeography/SqlGeometry UDTs)
409 internal static object EnsureSqlParameterValue(object value)
412 value != DBNull.Value &&
413 Type.GetTypeCode(value.GetType()) == TypeCode.Object)
415 // If the parameter is being created based on an actual value (typically for constants found in DML expressions) then a DbGeography/DbGeometry
416 // value must be replaced by an an appropriate Microsoft.SqlServer.Types.SqlGeography/SqlGeometry instance. Since the DbGeography/DbGeometry
417 // value may not have been originally created by this SqlClient provider services implementation, just using the ProviderValue is not sufficient.
418 DbGeography geographyValue = value as DbGeography;
419 if (geographyValue != null)
421 value = GetSqlTypesAssembly().ConvertToSqlTypesGeography(geographyValue);
425 DbGeometry geometryValue = value as DbGeometry;
426 if (geometryValue != null)
428 value = GetSqlTypesAssembly().ConvertToSqlTypesGeometry(geometryValue);
437 /// Determines SqlDbType for the given primitive type. Extracts facet
438 /// information as well.
440 private static SqlDbType GetSqlDbType(TypeUsage type, bool isOutParam, SqlVersion version, out int? size, out byte? precision, out byte? scale, out string udtName) {
441 // only supported for primitive type
442 PrimitiveTypeKind primitiveTypeKind = MetadataHelper.GetPrimitiveTypeKind(type);
444 size = default(int?);
445 precision = default(byte?);
446 scale = default(byte?);
447 udtName = default(string);
450 switch (primitiveTypeKind) {
451 case PrimitiveTypeKind.Binary:
452 // for output parameters, ensure there is space...
453 size = GetParameterSize(type, isOutParam);
454 return GetBinaryDbType(type);
456 case PrimitiveTypeKind.Boolean:
457 return SqlDbType.Bit;
459 case PrimitiveTypeKind.Byte:
460 return SqlDbType.TinyInt;
462 case PrimitiveTypeKind.Time:
463 if (!SqlVersionUtils.IsPreKatmai(version)) {
464 precision = GetKatmaiDateTimePrecision(type, isOutParam);
466 return SqlDbType.Time;
468 case PrimitiveTypeKind.DateTimeOffset:
469 if (!SqlVersionUtils.IsPreKatmai(version)) {
470 precision = GetKatmaiDateTimePrecision(type, isOutParam);
472 return SqlDbType.DateTimeOffset;
474 case PrimitiveTypeKind.DateTime:
475 //For katmai pick the type with max precision which is datetime2
476 if (!SqlVersionUtils.IsPreKatmai(version)) {
477 precision = GetKatmaiDateTimePrecision(type, isOutParam);
478 return SqlDbType.DateTime2;
481 return SqlDbType.DateTime;
484 case PrimitiveTypeKind.Decimal:
485 precision = GetParameterPrecision(type, null);
486 scale = GetScale(type);
487 return SqlDbType.Decimal;
489 case PrimitiveTypeKind.Double:
490 return SqlDbType.Float;
492 case PrimitiveTypeKind.Geography:
494 udtName = "geography";
495 return SqlDbType.Udt;
498 case PrimitiveTypeKind.Geometry:
500 udtName = "geometry";
501 return SqlDbType.Udt;
504 case PrimitiveTypeKind.Guid:
505 return SqlDbType.UniqueIdentifier;
507 case PrimitiveTypeKind.Int16:
508 return SqlDbType.SmallInt;
510 case PrimitiveTypeKind.Int32:
511 return SqlDbType.Int;
513 case PrimitiveTypeKind.Int64:
514 return SqlDbType.BigInt;
516 case PrimitiveTypeKind.SByte:
517 return SqlDbType.SmallInt;
519 case PrimitiveTypeKind.Single:
520 return SqlDbType.Real;
522 case PrimitiveTypeKind.String:
523 size = GetParameterSize(type, isOutParam);
524 return GetStringDbType(type);
527 Debug.Fail("unknown PrimitiveTypeKind " + primitiveTypeKind);
528 return SqlDbType.Variant;
533 /// Determines preferred value for SqlParameter.Size. Returns null
534 /// where there is no preference.
536 private static int? GetParameterSize(TypeUsage type, bool isOutParam) {
537 Facet maxLengthFacet;
538 if (type.Facets.TryGetValue(DbProviderManifest.MaxLengthFacetName, false, out maxLengthFacet) &&
539 null != maxLengthFacet.Value) {
540 if (maxLengthFacet.IsUnbounded) {
544 return (int?)maxLengthFacet.Value;
547 else if (isOutParam) {
548 // if the parameter is a return/out/inout parameter, ensure there
549 // is space for any value
554 return default(int?);
558 private static int GetNonMaxLength(SqlDbType type)
561 if (type == SqlDbType.NChar || type == SqlDbType.NVarChar)
565 else if(type == SqlDbType.Char || type == SqlDbType.VarChar ||
566 type == SqlDbType.Binary || type == SqlDbType.VarBinary)
573 private static int GetDefaultStringMaxLength(SqlVersion version, SqlDbType type)
576 if (version < SqlVersion.Sql9)
578 if (type == SqlDbType.NChar || type == SqlDbType.NVarChar)
594 private static int GetDefaultBinaryMaxLength(SqlVersion version)
597 if (version < SqlVersion.Sql9)
609 /// Returns SqlParameter.Precision where the type facet exists. Otherwise,
610 /// returns null or the maximum available precision to avoid truncation (which can occur
611 /// for output parameters).
613 private static byte? GetKatmaiDateTimePrecision(TypeUsage type, bool isOutParam) {
614 byte? defaultIfUndefined = isOutParam ? (byte?)7 : (byte?)null;
615 return GetParameterPrecision(type, defaultIfUndefined);
619 /// Returns SqlParameter.Precision where the type facet exists. Otherwise,
622 private static byte? GetParameterPrecision(TypeUsage type, byte? defaultIfUndefined) {
624 if (TypeHelpers.TryGetPrecision(type, out precision)) {
628 return defaultIfUndefined;
633 /// Returns SqlParameter.Scale where the type facet exists. Otherwise,
636 private static byte? GetScale(TypeUsage type) {
638 if (TypeHelpers.TryGetScale(type, out scale)) {
642 return default(byte?);
647 /// Chooses the appropriate SqlDbType for the given string type.
649 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
650 private static SqlDbType GetStringDbType(TypeUsage type) {
651 Debug.Assert(type.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType &&
652 PrimitiveTypeKind.String == ((PrimitiveType)type.EdmType).PrimitiveTypeKind, "only valid for string type");
655 if (type.EdmType.Name.ToLowerInvariant() == "xml") {
656 dbType = SqlDbType.Xml;
659 // Specific type depends on whether the string is a unicode string and whether it is a fixed length string.
660 // By default, assume widest type (unicode) and most common type (variable length)
663 if (!TypeHelpers.TryGetIsFixedLength(type, out fixedLength)) {
667 if (!TypeHelpers.TryGetIsUnicode(type, out unicode)) {
672 dbType = (unicode ? SqlDbType.NChar : SqlDbType.Char);
675 dbType = (unicode ? SqlDbType.NVarChar : SqlDbType.VarChar);
682 /// Chooses the appropriate SqlDbType for the given binary type.
684 private static SqlDbType GetBinaryDbType(TypeUsage type) {
685 Debug.Assert(type.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType &&
686 PrimitiveTypeKind.Binary == ((PrimitiveType)type.EdmType).PrimitiveTypeKind, "only valid for binary type");
688 // Specific type depends on whether the binary value is fixed length. By default, assume variable length.
690 if (!TypeHelpers.TryGetIsFixedLength(type, out fixedLength)) {
694 return fixedLength ? SqlDbType.Binary : SqlDbType.VarBinary;
697 protected override string DbCreateDatabaseScript(string providerManifestToken, StoreItemCollection storeItemCollection)
699 EntityUtil.CheckArgumentNull(providerManifestToken, "providerManifestToken");
700 EntityUtil.CheckArgumentNull(storeItemCollection, "storeItemCollection");
701 SqlVersion version = SqlVersionUtils.GetSqlVersion(providerManifestToken);
702 return CreateObjectsScript(version, storeItemCollection);
706 /// Create the database and the database objects.
707 /// If initial catalog is not specified, but AttachDBFilename is specified, we generate a random database name based on the AttachDBFilename.
708 /// Note: this causes pollution of the db, as when the connection string is later used, the mdf will get attached under a different name.
709 /// However if we try to replicate the name under which it would be attached, the following scenario would fail:
710 /// The file does not exist, but registered with database.
711 /// The user calls: If (DatabaseExists) DeleteDatabase
713 /// For further details on the behavior when AttachDBFilename is specified see Dev10# 188936
715 protected override void DbCreateDatabase(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection)
717 EntityUtil.CheckArgumentNull(connection, "connection");
718 EntityUtil.CheckArgumentNull(storeItemCollection, "storeItemCollection");
720 SqlConnection sqlConnection = SqlProviderUtilities.GetRequiredSqlConnection(connection);
721 string databaseName, dataFileName, logFileName;
722 GetOrGenerateDatabaseNameAndGetFileNames(sqlConnection, out databaseName, out dataFileName, out logFileName);
723 string createDatabaseScript = SqlDdlBuilder.CreateDatabaseScript(databaseName, dataFileName, logFileName);
724 SqlVersion sqlVersion = GetSqlVersion(storeItemCollection);
726 string createObjectsScript = CreateObjectsScript(sqlVersion, storeItemCollection);
728 UsingMasterConnection(sqlConnection, conn =>
731 CreateCommand(conn, createDatabaseScript, commandTimeout).ExecuteNonQuery();
734 // Create database already succeeded. If there is a failure from this point on, the user should be informed.
737 // Clear connection pool for the database connection since after the 'create database' call, a previously
738 // invalid connection may now be valid.
739 SqlConnection.ClearPool(sqlConnection);
741 UsingConnection(sqlConnection, conn =>
743 // create database objects
744 CreateCommand(conn, createObjectsScript, commandTimeout).ExecuteNonQuery();
749 if (EntityUtil.IsCatchableExceptionType(e))
751 // Try to drop the database
754 DropDatabase(sqlConnection, commandTimeout, databaseName);
758 // The creation of the database succeeded, the creation of the database objects failed, and the dropping of the database failed.
759 if (EntityUtil.IsCatchableExceptionType(ie))
761 throw new InvalidOperationException(Strings.SqlProvider_IncompleteCreateDatabase, new AggregateException(Strings.SqlProvider_IncompleteCreateDatabaseAggregate, e, ie));
765 // The creation of the database succeeded, the creation of the database objects failed, the database was dropped, no reason to wrap the exception
772 private static SqlVersion GetSqlVersion(StoreItemCollection storeItemCollection)
774 SqlProviderManifest sqlManifest = (storeItemCollection.StoreProviderManifest as SqlProviderManifest);
775 if (sqlManifest == null)
777 throw EntityUtil.Argument(System.Data.Entity.Strings.Mapping_Provider_WrongManifestType(typeof(SqlProviderManifest)));
779 SqlVersion sqlVersion = sqlManifest.SqlVersion;
783 private static void GetOrGenerateDatabaseNameAndGetFileNames(SqlConnection sqlConnection, out string databaseName, out string dataFileName, out string logFileName)
785 Debug.Assert(sqlConnection != null);
787 var connectionStringBuilder = new SqlConnectionStringBuilder(sqlConnection.ConnectionString);
789 // Get the file names
790 string attachDBFile = connectionStringBuilder.AttachDBFilename;
791 if (string.IsNullOrEmpty(attachDBFile))
798 //Handle the other cases
799 dataFileName = GetMdfFileName(attachDBFile);
800 logFileName = GetLdfFileName(dataFileName);
803 // Get the database name
804 if (!string.IsNullOrEmpty(connectionStringBuilder.InitialCatalog))
806 databaseName = connectionStringBuilder.InitialCatalog;
808 else if (dataFileName != null)
810 //generate the database name here
811 databaseName = GenerateDatabaseName(dataFileName);
815 throw EntityUtil.InvalidOperation(Strings.SqlProvider_DdlGeneration_MissingInitialCatalog);
820 /// Get the Ldf name given the Mdf full name
822 private static string GetLdfFileName(string dataFileName)
825 var directory = new FileInfo(dataFileName).Directory;
826 logFileName = Path.Combine(directory.FullName, String.Concat(Path.GetFileNameWithoutExtension(dataFileName), "_log.ldf"));
831 /// Generates database name based on the given mdfFileName.
832 /// The logic is replicated from System.Web.DataAccess.SqlConnectionHelper
834 private static string GenerateDatabaseName(string mdfFileName)
836 string toUpperFileName = mdfFileName.ToUpper(CultureInfo.InvariantCulture);
837 char [] strippedFileNameChars = Path.GetFileNameWithoutExtension(toUpperFileName).ToCharArray();
839 for (int iter = 0; iter < strippedFileNameChars.Length; iter++)
841 if (!char.IsLetterOrDigit(strippedFileNameChars[iter]))
843 strippedFileNameChars[iter] = '_';
847 string strippedFileName = new string(strippedFileNameChars);
848 strippedFileName = strippedFileName.Length > 30 ? strippedFileName.Substring(0, 30) : strippedFileName;
850 string databaseName = databaseName = String.Format(CultureInfo.InvariantCulture, "{0}_{1}", strippedFileName, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture));
855 /// Get the full mdf file name given the attachDBFile value from the connection string
857 /// <param name="attachDBFile"></param>
858 /// <returns></returns>
859 private static string GetMdfFileName(string attachDBFile)
861 Debug.Assert(!string.IsNullOrEmpty(attachDBFile));
863 //Handle the case when attachDBFilename starts with |DataDirectory|
864 string dataFileName = System.Data.EntityClient.DbConnectionOptions.ExpandDataDirectory("AttachDBFilename", attachDBFile);
866 //Handle the other cases
867 dataFileName = dataFileName ?? attachDBFile;
872 /// Determines whether the database for the given connection exists.
873 /// There are three cases:
874 /// 1. Initial Catalog = X, AttachDBFilename = null: (SELECT Count(*) FROM sys.databases WHERE [name]= X) > 0
875 /// 2. Initial Catalog = X, AttachDBFilename = F: if (SELECT Count(*) FROM sys.databases WHERE [name]= X) > true,
876 /// if not, try to open the connection and then return (SELECT Count(*) FROM sys.databases WHERE [name]= X) > 0
877 /// 3. Initial Catalog = null, AttachDBFilename = F: Try to open the connection. If that succeeds the result is true, otherwise
878 /// if the there are no databases corresponding to the given file return false, otherwise throw.
880 /// Note: We open the connection to cover the scenario when the mdf exists, but is not attached.
881 /// Given that opening the connection would auto-attach it, it would not be appropriate to return false in this case.
882 /// Also note that checking for the existence of the file does not work for a remote server. (Dev11 #290487)
883 /// For further details on the behavior when AttachDBFilename is specified see Dev10# 188936
885 protected override bool DbDatabaseExists(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection)
887 EntityUtil.CheckArgumentNull(connection, "connection");
888 EntityUtil.CheckArgumentNull(storeItemCollection, "storeItemCollection");
890 SqlConnection sqlConnection = SqlProviderUtilities.GetRequiredSqlConnection(connection);
891 var connectionBuilder = new SqlConnectionStringBuilder(sqlConnection.ConnectionString);
893 if (string.IsNullOrEmpty(connectionBuilder.InitialCatalog) && string.IsNullOrEmpty(connectionBuilder.AttachDBFilename))
895 throw EntityUtil.InvalidOperation(Strings.SqlProvider_DdlGeneration_MissingInitialCatalog);
898 if (!string.IsNullOrEmpty(connectionBuilder.InitialCatalog))
900 if (CheckDatabaseExists(sqlConnection, commandTimeout, connectionBuilder.InitialCatalog))
902 //Avoid further processing
907 if (!string.IsNullOrEmpty(connectionBuilder.AttachDBFilename))
911 UsingConnection(sqlConnection, (SqlConnection con) => { });
914 catch (SqlException e)
916 if (!string.IsNullOrEmpty(connectionBuilder.InitialCatalog))
918 return CheckDatabaseExists(sqlConnection, commandTimeout, connectionBuilder.InitialCatalog);
920 // Initial catalog not specified
921 string fileName = GetMdfFileName(connectionBuilder.AttachDBFilename);
922 bool databaseDoesNotExistInSysTables = false;
923 UsingMasterConnection(sqlConnection, conn =>
925 SqlVersion sqlVersion = SqlVersionUtils.GetSqlVersion(conn);
926 string databaseExistsScript = SqlDdlBuilder.CreateCountDatabasesBasedOnFileNameScript(fileName, useDeprecatedSystemTable: sqlVersion == SqlVersion.Sql8);
927 int result = (int)CreateCommand(conn, databaseExistsScript, commandTimeout).ExecuteScalar();
928 databaseDoesNotExistInSysTables = (result == 0);
930 if (databaseDoesNotExistInSysTables)
934 throw EntityUtil.InvalidOperation(Strings.SqlProvider_DdlGeneration_CannotTellIfDatabaseExists, e);
938 // CheckDatabaseExists returned false and no AttachDBFilename is specified
942 private static bool CheckDatabaseExists(SqlConnection sqlConnection, int? commandTimeout, string databaseName)
944 bool databaseExistsInSysTables = false;
945 UsingMasterConnection(sqlConnection, conn =>
947 SqlVersion sqlVersion = SqlVersionUtils.GetSqlVersion(conn);
948 string databaseExistsScript = SqlDdlBuilder.CreateDatabaseExistsScript(databaseName, useDeprecatedSystemTable: sqlVersion == SqlVersion.Sql8);
949 int result = (int)CreateCommand(conn, databaseExistsScript, commandTimeout).ExecuteScalar();
950 databaseExistsInSysTables = (result > 0);
952 return databaseExistsInSysTables;
956 /// Delete the database for the given connection.
957 /// There are three cases:
958 /// 1. If Initial Catalog is specified (X) drop database X
959 /// 2. Else if AttachDBFilename is specified (F) drop all the databases corresponding to F
961 /// 3. If niether the catalog not the file name is specified - throw
963 /// Note that directly deleting the files does not work for a remote server. However, even for not attached
964 /// databases the current logic would work assuming the user does: if (DatabaseExists) DeleteDatabase
966 /// <param name="connection"></param>
967 /// <param name="commandTimeout"></param>
968 /// <param name="storeItemCollection"></param>
969 protected override void DbDeleteDatabase(DbConnection connection, int? commandTimeout, StoreItemCollection storeItemCollection)
971 EntityUtil.CheckArgumentNull(connection, "connection");
972 EntityUtil.CheckArgumentNull(storeItemCollection, "storeItemCollection");
973 SqlConnection sqlConnection = SqlProviderUtilities.GetRequiredSqlConnection(connection);
975 var connectionBuilder = new SqlConnectionStringBuilder(sqlConnection.ConnectionString);
976 string initialCatalog = connectionBuilder.InitialCatalog;
977 string attachDBFile = connectionBuilder.AttachDBFilename;
979 if (!string.IsNullOrEmpty(initialCatalog))
981 DropDatabase(sqlConnection, commandTimeout, initialCatalog);
984 // initial catalog not specified
985 else if (!string.IsNullOrEmpty(attachDBFile))
987 string fullFileName = GetMdfFileName(attachDBFile);
989 List<string> databaseNames = new List<string>();
990 UsingMasterConnection(sqlConnection, conn =>
992 SqlVersion sqlVersion = SqlVersionUtils.GetSqlVersion(conn);
993 string getDatabaseNamesScript = SqlDdlBuilder.CreateGetDatabaseNamesBasedOnFileNameScript(fullFileName, sqlVersion == SqlVersion.Sql8);
994 var command = CreateCommand(conn, getDatabaseNamesScript, commandTimeout);
995 using (var reader = command.ExecuteReader())
997 while (reader.Read())
999 databaseNames.Add(reader.GetString(0));
1003 if (databaseNames.Count > 0)
1005 foreach (var databaseName in databaseNames)
1007 DropDatabase(sqlConnection, commandTimeout, databaseName);
1012 throw EntityUtil.InvalidOperation(Strings.SqlProvider_DdlGeneration_CannotDeleteDatabaseNoInitialCatalog);
1015 // neither initial catalog nor attachDB file name are specified
1018 throw EntityUtil.InvalidOperation(Strings.SqlProvider_DdlGeneration_MissingInitialCatalog);
1022 private static void DropDatabase(SqlConnection sqlConnection, int? commandTimeout, string databaseName)
1024 // clear the connection pool in case someone's holding on to the database still
1025 SqlConnection.ClearPool(sqlConnection);
1027 string dropDatabaseScript = SqlDdlBuilder.DropDatabaseScript(databaseName);
1028 UsingMasterConnection(sqlConnection, (conn) =>
1030 CreateCommand(conn, dropDatabaseScript, commandTimeout).ExecuteNonQuery();
1034 private static string CreateObjectsScript(SqlVersion version, StoreItemCollection storeItemCollection)
1036 return SqlDdlBuilder.CreateObjectsScript(storeItemCollection, createSchemas: version != SqlVersion.Sql8);
1039 private static SqlCommand CreateCommand(SqlConnection sqlConnection, string commandText, int? commandTimeout)
1041 Debug.Assert(sqlConnection != null);
1042 if (string.IsNullOrEmpty(commandText))
1044 // SqlCommand will complain if the command text is empty
1045 commandText = Environment.NewLine;
1047 var command = new SqlCommand(commandText, sqlConnection);
1048 if (commandTimeout.HasValue)
1050 command.CommandTimeout = commandTimeout.Value;
1055 private static void UsingConnection(SqlConnection sqlConnection, Action<SqlConnection> act)
1057 // remember the connection string so that we can reset it credentials are wiped
1058 string holdConnectionString = sqlConnection.ConnectionString;
1059 bool openingConnection = sqlConnection.State == ConnectionState.Closed;
1060 if (openingConnection)
1062 sqlConnection.Open();
1070 if (openingConnection && sqlConnection.State == ConnectionState.Open)
1072 // if we opened the connection, we should close it
1073 sqlConnection.Close();
1075 if (sqlConnection.ConnectionString != holdConnectionString)
1077 sqlConnection.ConnectionString = holdConnectionString;
1082 private static void UsingMasterConnection(SqlConnection sqlConnection, Action<SqlConnection> act)
1084 var connectionBuilder = new SqlConnectionStringBuilder(sqlConnection.ConnectionString)
1086 InitialCatalog = "master",
1087 AttachDBFilename = string.Empty, // any AttachDB path specified is not relevant to master
1092 using (var masterConnection = new SqlConnection(connectionBuilder.ConnectionString))
1094 UsingConnection(masterConnection, act);
1097 catch (SqlException e)
1099 // if it appears that the credentials have been removed from the connection string, use an alternate explanation
1100 if (!connectionBuilder.IntegratedSecurity &&
1101 (string.IsNullOrEmpty(connectionBuilder.UserID) || string.IsNullOrEmpty(connectionBuilder.Password)))
1103 throw new InvalidOperationException(Strings.SqlProvider_CredentialsMissingForMasterConnection, e);