I mentioned in my post yesterday that SQL Server Compact Edition doesn't support multiple concurrent connections to a database file located on a network share. In systems where we control the configuration avoiding this scenario is easy. It occurs to me, however, that many systems are end-user configurable which means that the user may choose a network share as the home for the database file for any number of reasons.
As a result, it's important for any Notebook/Desktop computer or Ultra-Mobile PC application that might possibly have multiple concurrent connections to a SQL Server Compact Edition database to include a check to verify that the database file is not located on a network share. Failing to do so, could lead to difficult to diagnose system issues down the road.
The .NET code to determine a path's drive type uses the .NET Framework's System.Management.ManagementObject class. For simplicity I've wrapped the various steps into individual functions. All of the functions work with both absolute and relative pathnames and the path does not have to actually exist.
- IsPathOnNetworkDrive: Identify whether a file is located on a network share
- GetDriveTypeForPath: Determine a file path's drive type (local, network share, removable, etc.)
- GetDriveNameForPath: Retrieve the drive specification for a path (C:, K:, Z:, etc.)
/// <summary>
/// Determines whether a file path is located on a Network Share
/// </summary>
/// <param name="pathName">Absolute or relative pathname of the file.
/// The pathname is not required to currently exist</param>
/// <returns>True if file path is located on a Network Share</returns>
static bool IsPathOnNetworkDrive(string pathName)
{
return GetDriveTypeForPath(pathName) == DriveType.Network;
}
/// <summary>
/// Identify a file path's drive type (local disk, network share, removable drive, etc.)
/// </summary>
/// <param name="pathName">Absolute or relative pathname of the file
/// The pathname is not required to currently exist</param>
/// <returns>A DriveType enumeration value identifying the device type where the file path is located</returns>
static DriveType GetDriveTypeForPath(string pathName)
{
DriveType driveType = DriveType.Unknown;
string driveName = GetDriveNameForPath(pathName);
string driveObjectname = string.Format("Win32_LogicalDisk.DeviceID=\"{0}\"", driveName);
ManagementObject driveObj = new ManagementObject(driveObjectname);
driveObj.Get();
PropertyData driveTypePropertyData = driveObj.Properties["DriveType"];
if (driveTypePropertyData.Value != null)
driveType = (DriveType)Convert.ToInt32(driveTypePropertyData.Value);
return driveType;
}
/// <summary>
/// Identifies the drive portion of a file path
/// </summary>
/// <param name="pathName">Absolute or relative pathname of the file
/// The pathname is not required to currently exist</param>
/// <returns>A string containing the drive portion of the file path. The drive string contains the colon
/// i.e. C:, K:, Z:, etc.</returns>
static string GetDriveNameForPath(string pathName)
{
string pathRoot = Directory.GetDirectoryRoot(pathName);
int idxSeperator = pathRoot.IndexOf(Path.VolumeSeparatorChar);
string driveName = pathRoot.Substring(0, idxSeperator + 1);
return driveName;
}
What I found kind'a interesting is the difference between the .NET code and the native code to determine if a path is on a network share. It turns out that for native developers, one can just use the GetDeviceType API call rather then needing to create and access the management objects as .NET requires. I understand why the .NET code is so different; it's using a standardized mechanism for identifying and managing parts of a system. That said, in this case it does seem like a fair amount of overhead.
If you don't mind using P/Invoke to call out to native code, you can call out to the Win32 GetDriveType API as is done in the following version of GetDriveTypeForPath which is much more lightweight than the version of GetDriveTypeForPath shown above.
/// <summary>
/// Win32 P/Invoke to GetDriveType
/// </summary>
/// <param name="pathName">Drive specification including the colon - i.e. C:, K:, Z:, etc.</param>
/// <returns>An UInt32 value identifying the drive type. Actual #define values are defined in
/// WinBase.h - the values returned by this function correspond directly to the .NET DriveType enumeration</returns>
[DllImport("Kernel32.dll")]
static extern UInt32 GetDriveType(string pathName);
/// <summary>
/// Identify the type of drive (local disk, network share, removable drive, etc.) a file path
/// is located on.
/// </summary>
/// <remarks>This function uses P/Invoke to call out to a Win32 API function and therefore may introduce
/// significant Code Access Security Concerns</remarks>
/// <param name="pathName">Absolute or relative pathname of the file
/// The pathname is not required to currently exist</param>
/// <returns>A DriveType enumeration value identifying the device type where the file path is located</returns>
static DriveType GetDriveTypeForPath(string pathName)
{
string driveName = GetDriveNameForPath(pathName);
return (DriveType) Convert.ToInt32(GetDriveType(driveName));
}
As lightweight as the second choice is, it does introduce significant concerns from a Code Access Security standpoint because you're calling directly into native code. In general I'd gravitate to the first (pure .NET) solution but I wanted to show both choices.
Whatever technique you choose to use, from the standpoint of SQL Server Compact Edition, the most important thing is that any application that opens multiple concurrent connections to a SQL Server Compact Edition database include a check to verify that the database isn't located on a network share.
BTW: I want to thank Ted Neward, Dan Sullivan, and Jason Whittington for their help and opinions during the debate over the two GetDriveTypeForPath implementations. J
Posted
Feb 19 2007, 08:27 AM
by
jim-wilson