There is a lot of confusing documentation, opinion and debate when it comes to database terminology, database design dos and don'ts and best practices. This is especially prevalent when it comes to the subtle differences between seemingly similar relational database "key" terminology/constructs, such as candidate keys, super keys, surrogate keys, natural keys, primary keys, alternate keys, unique keys, unique constraints, unique indexes and foreign keys.
I will attempt to uncover some of the subtle, but very pertinent, differences between these confusing concepts and terminology.
Candidate, Primary, Alternate and Unique Keys
Very simply, any combination of one or more attributes (columns), whose combined value is unique among all tuples (rows) is a candidate key (CK) on a relation (table). That is, any unique column or unique combination of columns is a candidate key on a table.
The primary key (PK) of any table is any candidate key of that table which the database designer arbitrarily designates as "primary". The primary key may be selected for convenience, comprehension, performance, or any other reasons. It is entirely proper (albeit often inconvenient) to change the selection of primary key to another candidate key.
The alternate keys (AK) of any table are simply those candidate keys which are not currently selected as the primary key. Exactly one of those candidate keys is chosen as the primary key and the remainder, if any, are then called alternate keys. An alternate key is a function of all candidate keys minus the primary key.
There can be many candidate keys on a table. The terminology "candidate key" implies that one of these keys can be selected as the primary key -- there can be only one primary key on a table (more about primary keys below). That is, all of the candidate keys are potential primary keys, hence the name "candidate key". It is theoretically possible that a table has no candidate keys and therefore no primary key. This is a hot topic of debate, since many relational database purists insist that all tables must have a primary key, and therefore must have at least one candidate key.
Many data modelling products do not adequately represent the concept of a candidate key. This is unfortunate as much metadata in the model definition is lost as a result.
For example:
Lets say there is an Employee table, a Skill table and an EmployeeSkill association table associating skills with employees:
EmployeeSkill
In the table above, the primary key (PK) is across both the EmployeeId and the SkillId columns.
Lets say that later on we decide to add a database generated, numeric integer, surrogate key column called EmployeeSkillId and designate it as the new primary key:
EmployeeSkill
| PK | EmployeeSkillId |
| | EmployeeId |
| | SkillId |
By adding the EmployeeSkillId column and changing the primary key to it, the model has lost the notion that EmployeeId and SkillId together form a candidate key (alternate key) and are therefore unique. That is, their uniqueness is no longer automatically enforced by the model.
In the database model, the candidate keys (CK) should first be represented and then one of those candidate keys marked as the primary key, thus making the remaining candidate keys alternate keys (AK) as follows:
| CK1 | EmployeeSkillId |
| CK2 | EmployeeId |
| CK2 | SkillId |
The model metadata would now be as follows:
| PK (CK1) | EmployeeSkillId |
| AK1 (CK2) | EmployeeId |
| AK1 (CK2) | SkillId |
This ensures that the EmployeeId/SkillId composite, alternate key still enforces uniqueness in the model. If we change the primary key to the second candidate key, there is sufficient metadata to preserve the notion of a unique EmployeeSkillId alternate key.
| AK (CK1) | EmployeeSkillId |
| PK (CK2) | EmployeeId |
| PK (CK2) | SkillId |
A characteristic of a candidate key is that it should never allows NULLs. This is important, because a candidate key is a potential primary key, and a primary key must never be NULL because it is used to identify a row. Likewise an alternate key must never allow NULLs for the same reason.
A candidate key must be unique. That is, a candidate key identifies a row uniquely, and therefore so does a primary key and an alternate key.
A very important, but often overlooked, notion of a candidate key, and therefore a primary key and an alternate key as well, is the notion of immutability.
A candidate key must always be immutable!
There is a distinct difference between truly, uniquely identifying a row and simply distinguishing a row from other rows. For instance a company has a name and a registration number. Both are unique and "identifying". But, the name is not immutable, since a company can change its name, while the registration number is immutable as the registration number never changes. Thus, although the company name can be used as a unique key to identify the company at a point in time, it cannot TRUELY identify the company through its entire lifetime -- as the name can change over time, and thus is not a candidate key (or a primary or alternate key for that matter).
This is an important and probably the most significant distinction between a candidate key (and therefore a primary and alternate key as well) and a unique key.
By enforcing that all candidate keys (primary and alternate keys) are immutable, unique and not nullable, they are truly identifying. This is an important characteristic of a candidate key, since a primary key and/or alternate keys can be used to persist references to an entity outside of the database model -- perhaps in another database. For this reason, primary and alternate keys should never be updated. Even if you allow cascaded updates within your data model, you cannot know who is holding a reference to your primary or alternate keys outside of your data model.
If you did allow updating of a primary or alternate key, it would imply that you had some knowledge outside of the data model, that the row before the update and after the update represents the same entity. This implies that your data model is wrong, because this knowledge is not represented in the data model. In fact there would be no distinction between an update and a delete followed by an insert.
In summary, a candidate key must have the following properties:
1. Must NOT be NULL. You must not have the notion of a missing identity for an entity.
2. Must be unique.
3. Must be immutable.
One candidate key must be selected as the primary key -- all other candidate keys become the alternate keys, which adhere to the same rules as a candidate key.
A unique key, that is not a candidate key (primary key or alternate key), is mutable.
Unique Index versus Unique Constraint
To define a unique key (not a primary key or an alternate key), SQL Server offers two implementations, a unique index and a unique constraint. Although these are both implemented in SQL Server as unique indexes under the hood, there are important differences between the two.
I have seen many posts touting that unique constraints and unique indexes are essentially the same. Many posts even highlight the implementation differences between the two concepts, such as FILLFACTOR that can be specified on a unique index, but not on a unique constraint. However, none of the posts I have read, have ever highlighted the most important and significant difference between these two concepts.
Conceptually, a unique constraint is a schema enforcing construct. That is, it enforces uniqueness on the column or columns to which it applies. A unique index implies something completely different from a modelling point of view. An index implies something added in addition to the schema definition, to improve performance.
Conceptually a constraint is a real schema modelling construct, which enforces integrity in the model, while an index is created to improve query performance. Thus I might decide to drop a unique index and create another index, while performance tuning, and inadvertently break the constraints of the data model schema.
If something is truly unique with respect to the conceptual/logical data model, then it must be defined as a unique constraint to adequately represent the notion of the constraint. However, if you define an index across one or more columns to improve performance, and that index happens to include a column or columns, which make it unique, then you should define this as a unique index, in addition to the unique constraint on the unique columns to preserve the conceptual/logical data model.
For example:
Employee
If IdNumber in the Employee table above is unique, it should be defined as a unique constraint in the data model.
If you often query on FirstName and return the IdNumber, you may choose to create an index, to improve performance, on FirstName and IdNumber. This index is unique because it included the IdNumber column which is unique and should be created as a unique index in addition to the unique constraint.
I often run into small, annoying little problems as I'm sure most people working in IT do at some point, so I decided to start blogging the solutions. No matter how insignificant they may seem, they may save someone an hour or two of Google'ing for the answer.
How to: Reindex an entire database on SQL Server 2000 or SQL Server 2005 in a single statement:
EXEC [sp_MSforeachtable] @command1="DBCC DBREINDEX('?')"
If you want to receive progress messages while it is running, try this version:
EXEC [sp_MSforeachtable] @command1="RAISERROR('DBCC DBREINDEX(''?'') ...',10,1) WITH NOWAIT DBCC DBREINDEX('?')"
I thought this was pretty cool...
The Microsoft IE team sent the Mozilla Firefox team a cake congratulating them on shipping Firefox 2.0 on 24 Oct 2006.

There is a security "problem"/issue using the XslCompiledTransform.Load() method to load and compile an XSLT stylesheet that contains script (C#, JScript or VB) under IIS/ASP.NET on Windows Server 2003 using Windows authentication and impersonation.
If you have used the XslTransform class in .NET 1.1 to transform XML data using an XSLT stylesheet, then you will know that when you upgrade to .NET 2.0, that this class is now obsolete ("This class has been deprecated. Please use System.Xml.Xsl.XslCompiledTransform instead. http://go.microsoft.com/fwlink/?linkid=14202").
The replacement class (XslCompiledTransform) now compiles the XSLT and then uses the compiled XSLT to perform the XML transform. There is one caveat with this approach. That is, if your XSLT stylesheet contains script of any kind, for instance C#, then the class will generate a temporary compiled assembly DLL in the system's temporary folder - and this is where the trouble with security comes in.
On Windows Server 2003, the .NET 2.0 XslCompiledTransform class attempts to write a temporary 8x3 DLL to C:\WINDOWS\Temp. If you have configured your site, remoting object or other ASP.NET hosted "thing" to use Windows Authentication and to impersonate the current logged on user, then the current logged on user will not typically be a user on the Server and thus does not have rights to write to C:\WINDOWS\Temp.
To get around this problem, I removed the script from my XSLT stylesheet, rather that opening up security for everyone to have write access to C:\WINDOWS\Temp.
IMHO this is an oversight on Microsoft's part. A BCL class should not be attempting to write out arbitrary files/data to the HDD as the BCL cannot assume under what circumstances it will be operating - e.g. across a network share, over click-once, or under ASP.NET. This should definately at least be documented in the MSDN documentation for the class, especially since XslTransform did not do this and has now been obsoleted. This is also not a problem on Windows 2000, although I am not sure why as I havent yet investigated the issue on Windows 2000.
Well I'm back from Tech-Ed 2006. Overall I'd have to say that the technology highlight for me was seeing LINQ in action. The best speaker in my opinion was Kimberly L. Tripp (http://www.sqlskills.com/blogs/kimberly/) who gave a wealth of SQL 2005 presentations/demos on all sorts of fascinating bits and pieces. I must say I picked up the most new tips and tricks from Kimberly's presentations. Kimberly managed to make even the most boring topics interesting to listen to - she clearly has some extreme SQL/SQL Server knowledge and in one demo she even quoted exact KB article numbers off the top of her head.
On the downside, I was hoping for more in-depth sessions on the new technologies WF, WPF and WCF. The sessions were few and far between and only touched the surface; leaving much to be desired.
On the upside, the parties were kick ass, the food was really good and the fireworks/laser show at the closing party was truely spectacular.
And did I mention that it was HOT! 36 degrees Celcius.
I don't know if it is just because I am tired (in the IT sense) or because I am just busy or simply because I have just become lazy, but I like out of the box stuff and I like it to work without having to read a 400 page help file and spend hours on Google searching for the answers. I don't have the energy to stuff around with installers of all things.
Let the stuffing around begin...
Anyway, so we have a complex solution which uses .vdproj Visual Studio 2005 Setup and Deployment projects to build installers. Why I hear you ask? Well because they are there, they are integrated into the solution build and the IDE, they have a simple GUI interface, they don't require you to download and install and configure additional components, they are fully supported by Microsoft (being part of the official VS 2005 RTM and all) and they just work! Yippee, well not quite.
While .vdproj setup projects seemed like a good idea at the time, it turns out it wasnt. So here comes along Microsoft with their NAnt rip-off - MSBuild. But don't even get me started on NAnt - yet another failed open source initiative to add to the ever growing list of open source investments (gambles?) gone south (lets not mention NDoc with VS 2005, .NET 2.0 and generics). Having been burnt by investing hours of development in promising open source technologies such as NDoc and NAnt (that have had no significant work done on them in years and hardly work at all with the new Microsoft technologies), I have decided to stick with Microsoft. But it seems not even Microsoft as it turns out is a good gamble (investment?).
Take the VS 2005 solution files for instance. The project files in VS 2005 are native XML MSBuild files. Of course making the solution file a native XML MSBuild file as well would have been far too logical and way too much to ask, so in their infinite wisdom Microsoft decided to implement yet another useless format (YAUF?), which by the way is not even XML, to define solution files. Heaven only knows why the project files are XML MSBuild project files, while the solution file is some text flat file of a proprietary, unknown, undocumented format? And a flat files of all things? Ever heard of XML? Anyway that aside, at least MSBuild has functionality built in to parse this proprietary solution file and do something useful with it. Of course M$ could have saved some of the $'s if it had simply made the solution file an XML MSBuild project in the first place or at least just XML.
So here's the thing, I have a lovely self-contained solution file that opens up in the VS 2005 IDE and builds like a dream. My developers only have to install trusty VS 2005 and everything just works - no additional downloads, no additional configuration - makes for minimal maintenance and reduces configuration hell. But alas, along comes Team Foundation Server and MSBuild. Err, we don't support .vdproj projects??? What gives? Are you completely stupid or just lacking in brain cells Microsoft? Of course like everything there is a nifty little KB workaorund for the problem. Just install the whole of VS 2005 on the build server, modify all your setup projects in notepad to change all the paths to relative paths (and hope like hell you didn't corrupt the project in the process) and then use the trusty EXEC task in MSBuild to call the VS 2005 IDE to build each setup project individually. Of course if you add new setup projects to your solution you need to add these extra EXEC tasks to the MSBuild project and of course if you ever edit the setup .vdproj, remember to open it up again in trusty notepad and change all the paths. Great Microsoft! Well Done! We love productivity tools like this one.
Ok, so if I have to use MSBuild to build everything else, except the setup projects, and then use devenv.exe (VS 2005 IDE command line) to build the setup apps each individually, why the hell don't I just build the entire solution using devenv.exe and forget about MSBuild? So I'm thinking well maybe there are other cools tasks that MSBuild has (out of the box) that I can use to do more than what my trusty .cmd file that calls devenv.exe can do? Hmmm, I start by looking for the FxCop task? Oh they don't have one... great! However, I can call csc, al and all the rest of the compiler tools sepeartely?! DOH! Yes, I want to do this why? This is why I have a solution and projects to track the dependencies, the references and files to compile in the right order and why I use MSBuild on the solution file and not the individual .cs files. Ok, so there must be more (out of the box of course)... and true enough yes there is... I can copy files, make directories, delete files and do all sorts of nifty command line type things using XML?! HUH? But my .cmd file does this already, funny enough with less lines of "code" and far less angle brackets. Ever heard of the copy command, the md command and the del command?
Anyway that aside, I decide this mismatch of technology bloopers called VS 2005, MSBuild, Team Foundation Server and .vdproj Setup and Deployment projects is not working out. So time to look for an alternative. Of course I really don't want to buy yet another installer such as InstallShield or Wise and then go through yet another learning curve and use yet another proprietary tool (with its own quirks and upgrade path issues and licence issues) and yet another proprietary scripting language...
Hello WiX. Windows Installer XML. A project "open sourced" by Microsoft and used internally by Microsoft. Of course WiX being pure XML has amazing promise, but it lacks out the box'ness. You need to edit XML, which as much as I love XML starts to make my eyes go funny and hurt my hands typing all the angle brackets. There is still no useful GUI for WiX. In fact in the true spirit of open source, the project for the WiX GUI has had no movement for ages. So What to do... Problem 1, is I have to record somewhere that developers must download and install this thing otherwise setup projects are simply not going to compile... I guess I can sort of live with this additional configuration nightmare. However, how on earth do I get these things to build in and out of the VS 2005 IDE? Hello Votive. Votive creates projects for VS 2005 that incorporate the WiX XML into the build of the solution. There are also MSBuild tasks for WiX out the box. This all sounds great from a infrastructure/build point of view, except that I have to install something extra and remember to transfer this knowledge onto future developers. So far so good.
So I fire up Visual Studio 2005; create a new WiX project and hit build. Bloop 2 errors; must specifiy PUT-GUID-HERE. Ok Tools, Create GUID, Copy, paste, Build. Same errors? Oh it doesn't automatically save on build like C# projects do, ok, no problem, click save and then build again. Oops another error:
Error 6 ICE18: KeyPath for Component: 'ProductComponent' is Directory: 'INSTALLLOCATION'. The Directory/Component pair must be listed in the CreateFolders table. C:\Development\Test\WixProject3\WixProject3\Product.wxs 12
Lovely, I have no idea what that means. I can't even get the empty WiX project to build! GRRR... Does this mean I have to read the 400 page documentation? ARG! I guess so. And anyway, since I am editing XML and have no idea what all these elements and attributes do, I guess I have no choice but to go and learn it... but honestly who has 2 or 3 days to master an installer when there is other work to be done... GRRR
Creating temporary files in .NET is not something I'd usually have to think too long and hard about, but if you do, you soon realise the inadequacies associated with Path.GetTempFileName().
Recently I was required to create a stream class that is a hybrid between a memory stream and a file stream. The idea is simple; for all intents and purposes it acts like a giant buffer (similar to memory stream), however internally when memory exceeds a designated threshold, it begins using disk (via a temporary file). Of course, when the object instance goes out of scope, so must the buffer and therefore the temporary file must be cleaned up. I also wanted the temporary file to be secure, so that only the current user could access the file and in particular only the current object instance.
So what's wrong with Path.GetTempFileName()? First of all it's not temporary at all. If you forget to delete the file or your process dies unexpectedly before your code has a chance to clean up its mess, the temporary files created by Path.GetTempFileName() are left lying around waiting for some diligent network administrator to come along and clean them up.
The second major issue, which may or may not be an issue for you, although it probably should be, is that of security. How often does a coder write out sensitive data to a temp file while processing some large data and then forget to clean it up. Temporary files created by Path.GetTempFileName() are completly insecure. In fact, because the method creates a zero-byte file and then closes the handle to it before passing the name of the file back to the coder (who is simply going to re-open the file anyway), there is a window for malicious code to delete the file or change the security priveledges before your code has a chance to re-open the file and apply appropriate security.
There are two other lesser issues with Path.GetTempFileName(). The first is that for some reason, even though Path.GetTempFileName() calls the Windows API to create the temporary file, the temporary file attribute is not set on the file by default. On the Windows platform, if a file is marked as a temporary file, the cache is optimised to avoid writing to the file system if sufficient memory is available - a nifty performance feature. The second is the file name itself is very predictable, even more so that the Windows API version of the function, which makes it vunerable, due to .NET prefixing all temporary files with 'tmp'. .NET 2.0 provides a new method; Path.GetRandomFileName(), but unlike Path.GetTempFileName(), this method does none of the work required to actually guarantee the uniqueness of the file-name on disk or create the file. All the method does is return a cryptographically unpredicatble 8x3 file-system compliant file name.
In order to solve the problems metioned above, I used several existing .NET features:
1. Path.GetTempPath() gives us the system's current temporary path, which is also profile "aware".
2. Path.GetRandomFileName() provides a cryptogrphically unpredictable 8x3 file-system compliant file-name.
3. The .NET 2.0 FileStream supports advanced FileSystemRights and FileSecurity for disabling sharing and setting appropriate ACLs on the file.
4. The .NET FileOption.DeleteOnClose enumeration flag, ensures that the file created is truely temporary. Even if the process terminates unexpectedly, the Windows kernel will delete the file when the last handle to the file is closed.
5. The .NET FileAttribtues.Temporary enumeration flag, marks the file as a temporary file, which improves performance of the file caches.
Bringing this altogether:
public static class PathUtility
{
private const int defaultBufferSize = 0x1000; // 4KB
#region
GetSecureDeleteOnCloseTempFileStream
/// <summary>
/// Creates a unique, randomly named, secure, zero-byte temporary file on disk, which is automatically deleted when it is no longer in use. Returns the opened file stream.
/// </summary>
/// <remarks>
/// <para>The generated file name is a cryptographically strong, random string. The file name is guaranteed to be unique to the system's temporary folder.</para>
/// <para>The <see cref="GetSecureDeleteOnCloseTempFileStream"/> method will raise an <see cref="IOException"/> if no unique temporary file name is available. Although this is possible, it is highly improbable. To resolve this error, delete all uneeded temporary files.</para>
/// <para>The file is created as a zero-byte file in the system's temporary folder.</para>
/// <para>The file owner is set to the current user. The file security permissions grant full control to the current user only.</para>
/// <para>The file sharing is set to none.</para>
/// <para>The file is marked as a temporary file. File systems avoid writing data back to mass storage if sufficient cache memory is available, because an application deletes a temporary file after a handle is closed. In that case, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed.</para>
/// <para>The system deletes the file immediately after it is closed or the <see cref="FileStream"/> is finalized.</para>
/// </remarks>
/// <returns>The opened <see cref="FileStream"/> object.</returns>
public static FileStream GetSecureDeleteOnCloseTempFileStream()
{
return GetSecureDeleteOnCloseTempFileStream(defaultBufferSize, FileOptions.DeleteOnClose);
}
/// <summary>
/// Creates a unique, randomly named, secure, zero-byte temporary file on disk, which is automatically deleted when it is no longer in use. Returns the opened file stream with the specified buffer size.
/// </summary>
/// <remarks>
/// <para>The generated file name is a cryptographically strong, random string. The file name is guaranteed to be unique to the system's temporary folder.</para>
/// <para>The <see cref="GetSecureDeleteOnCloseTempFileStream"/> method will raise an <see cref="IOException"/> if no unique temporary file name is available. Although this is possible, it is highly improbable. To resolve this error, delete all uneeded temporary files.</para>
/// <para>The file is created as a zero-byte file in the system's temporary folder.</para>
/// <para>The file owner is set to the current user. The file security permissions grant full control to the current user only.</para>
/// <para>The file sharing is set to none.</para>
/// <para>The file is marked as a temporary file. File systems avoid writing data back to mass storage if sufficient cache memory is available, because an application deletes a temporary file after a handle is closed. In that case, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed.</para>
/// <para>The system deletes the file immediately after it is closed or the <see cref="FileStream"/> is finalized.</para>
/// </remarks>
/// <param name="bufferSize">A positive <see cref="Int32"/> value greater than 0 indicating the buffer size.</param>
/// <returns>The opened <see cref="FileStream"/> object.</returns>
public static FileStream GetSecureDeleteOnCloseTempFileStream(int bufferSize)
{
return GetSecureDeleteOnCloseTempFileStream(bufferSize, FileOptions.DeleteOnClose);
}
/// <summary>
/// Creates a unique, randomly named, secure, zero-byte temporary file on disk, which is automatically deleted when it is no longer in use. Returns the opened file stream with the specified buffer size and file options.
/// </summary>
/// <remarks>
/// <para>The generated file name is a cryptographically strong, random string. The file name is guaranteed to be unique to the system's temporary folder.</para>
/// <para>The <see cref="GetSecureDeleteOnCloseTempFileStream"/> method will raise an <see cref="IOException"/> if no unique temporary file name is available. Although this is possible, it is highly improbable. To resolve this error, delete all uneeded temporary files.</para>
/// <para>The file is created as a zero-byte file in the system's temporary folder.</para>
/// <para>The file owner is set to the current user. The file security permissions grant full control to the current user only.</para>
/// <para>The file sharing is set to none.</para>
/// <para>The file is marked as a temporary file. File systems avoid writing data back to mass storage if sufficient cache memory is available, because an application deletes a temporary file after a handle is closed. In that case, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed.</para>
/// <para>The system deletes the file immediately after it is closed or the <see cref="FileStream"/> is finalized.</para>
/// <para>Use the <paramref name="options"/> parameter to specify additional file options. You can specify <see cref="FileOptions.Encrypted"/> to encrypt the file contents using the current user account. Specify <see cref="FileOptions.Asynchronous"/> to enable overlapped I/O when using asynchronous reads and writes.</para>
/// </remarks>
/// <param name="bufferSize">A positive <see cref="Int32"/> value greater than 0 indicating the buffer size.</param>
/// <param name="options">A <see cref="FileOptions"/> value that specifies additional file options.</param>
/// <returns>The opened <see cref="FileStream"/> object.</returns>
public static FileStream GetSecureDeleteOnCloseTempFileStream(int bufferSize, FileOptions options)
{
FileStream fs = GetSecureFileStream(Path.GetTempPath(), bufferSize, options | FileOptions.DeleteOnClose);
File.SetAttributes(fs.Name, File.GetAttributes(fs.Name) | FileAttributes.Temporary);
return fs;
}
#endregion
#region
GetSecureTempFileStream
public static FileStream GetSecureTempFileStream()
{
return GetSecureTempFileStream(defaultBufferSize, FileOptions.None);
}
public static FileStream GetSecureTempFileStream(int bufferSize)
{
return GetSecureTempFileStream(bufferSize, FileOptions.None);
}
public static FileStream GetSecureTempFileStream(int bufferSize, FileOptions options)
{
FileStream fs = GetSecureFileStream(Path.GetTempPath(), bufferSize, options);
File.SetAttributes(fs.Name, File.GetAttributes(fs.Name) | FileAttributes.NotContentIndexed | FileAttributes.Temporary);
return fs;
}
#endregion
#region
GetSecureTempFileName
public static string GetSecureTempFileName()
{
return GetSecureTempFileName(false);
}
public static string GetSecureTempFileName(bool encrypted)
{
using (FileStream fs = GetSecureFileStream(Path.GetTempPath(), defaultBufferSize, encrypted ? FileOptions.Encrypted : FileOptions.None))
{
File.SetAttributes(fs.Name, File.GetAttributes(fs.Name) | FileAttributes.NotContentIndexed | FileAttributes.Temporary);
return fs.Name;
}
}
#endregion
#region
GetSecureFileName
public static string GetSecureFileName(string path)
{
return GetSecureFileName(path, false);
}
public static string GetSecureFileName(string path, bool encrypted)
{
using (FileStream fs = GetSecureFileStream(path, defaultBufferSize, encrypted ? FileOptions.Encrypted : FileOptions.None))
{
return fs.Name;
}
}
#endregion
#region
GetSecureFileStream
public static FileStream GetSecureFileStream(string path)
{
return GetSecureFileStream(path, defaultBufferSize, FileOptions.None);
}
public static FileStream GetSecureFileStream(string path, int bufferSize)
{
return GetSecureFileStream(path, bufferSize, FileOptions.None);
}
public static FileStream GetSecureFileStream(string path, int bufferSize, FileOptions options)
{
if (path == null)
throw new ArgumentNullException("path");
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize");
if ((options & ~(FileOptions.Asynchronous | FileOptions.DeleteOnClose | FileOptions.Encrypted | FileOptions.RandomAccess | FileOptions.SequentialScan | FileOptions.WriteThrough)) != FileOptions.None)
throw new ArgumentOutOfRangeException("options");
new FileIOPermission(FileIOPermissionAccess.Write, path).Demand();
SecurityIdentifier user = WindowsIdentity.GetCurrent().User;
FileSecurity fileSecurity = new FileSecurity();
fileSecurity.AddAccessRule(
new FileSystemAccessRule(user, FileSystemRights.FullControl, AccessControlType.Allow));
fileSecurity.SetAccessRuleProtection(
true, false);
fileSecurity.SetOwner(user);
// Attempt to create a unique file three times before giving up.
// It is highly improbable that there will ever be a name clash,
// therefore we do not check to see if the file first exists.
for (int attempt = 0; attempt < 3; attempt++)
{
try
{
return new FileStream(
Path.Combine(path, Path.GetRandomFileName()),
FileMode.CreateNew, FileSystemRights.FullControl,
FileShare.None, bufferSize, options, fileSecurity);
}
catch (IOException)
{
if (attempt == 2)
throw;
}
}
// This code can never be reached.
// The compiler thinks otherwise.
throw new IOException();
}
#endregion
}