如何使用C#安全地将数据保存到现有文件?

如何使用C#安全地将数据保存到现有文件?,c#,io,C#,Io,如何将数据安全地保存到C#中已经存在的文件中?我有一些数据被序列化到一个文件中,我很确定直接安全地保存到该文件不是一个好主意,因为如果出现任何错误,该文件将被破坏,并且以前的版本将丢失 这就是我到目前为止所做的: string tempFile = Path.GetTempFileName(); using (Stream tempFileStream = File.Open(tempFile, FileMode.Truncate)) { SafeXmlSerializer xmlFo

如何将数据安全地保存到C#中已经存在的文件中?我有一些数据被序列化到一个文件中,我很确定直接安全地保存到该文件不是一个好主意,因为如果出现任何错误,该文件将被破坏,并且以前的版本将丢失

这就是我到目前为止所做的:

string tempFile = Path.GetTempFileName();

using (Stream tempFileStream = File.Open(tempFile, FileMode.Truncate))
{
    SafeXmlSerializer xmlFormatter = new SafeXmlSerializer(typeof(Project));
    xmlFormatter.Serialize(tempFileStream, Project);
}

if (File.Exists(fileName)) File.Delete(fileName);
File.Move(tempFile, fileName);
if (File.Exists(tempFile)) File.Delete(tempFile);
问题是,当我试图保存到已存在的文件时,有时会出现异常,告诉我无法保存到已存在的文件。显然是第一个
文件.Delete(fileName)
没有立即删除该文件,而是在一段时间后删除。所以我在
File.Move(tempFile,fileName)中得到了一个异常因为文件存在,然后文件被删除,我的文件丢失

我在Dropbox中使用了其他文件应用程序,但不知怎的,它们不会把它搞砸。当我试图保存到Dropbox文件夹中的文件时,有时我会收到一条消息,告诉我该文件正在被使用或类似的东西,但我从未遇到过文件被擦除的问题

那么这里的标准/最佳实践是什么

好的,这是我在阅读了所有答案后得出的结论:

private string GetTempFileName(string dir)
{
    string name = null;
    int attempts = 0;
    do
    {
        name = "temp_" + Player.Math.RandomDigits(10) + ".hsp";
        attempts++;
        if (attempts > 10) throw new Exception("Could not create temporary file.");
    }
    while (File.Exists(Path.Combine(dir, name)));

    return name;
}

private void SaveProject(string fileName)
{
    bool originalRenamed = false;
    string tempNewFile = null;
    string oldFileTempName = null;

    try
    {
        tempNewFile = GetTempFileName(Path.GetDirectoryName(fileName));

        using (Stream tempNewFileStream = File.Open(tempNewFile, FileMode.CreateNew))
        {
            SafeXmlSerializer xmlFormatter = new SafeXmlSerializer(typeof(Project));
            xmlFormatter.Serialize(tempNewFileStream, Project);
        }

        if (File.Exists(fileName))
        {
            oldFileTempName = GetTempFileName(Path.GetDirectoryName(fileName));
            File.Move(fileName, oldFileTempName);
            originalRenamed = true;
        }

        File.Move(tempNewFile, fileName);
        originalRenamed = false;

        CurrentProjectPath = fileName;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally
    {
        if(tempNewFile != null) File.Delete(tempNewFile);

        if (originalRenamed) MessageBox.Show("'" + fileName + "'" +
            " have been corrupted or deleted in this operation.\n" +
            "A backup copy have been created at '" + oldFileTempName + "'");
        else if (oldFileTempName != null) File.Delete(oldFileTempName);
    }
}
Player.Math.RandomDigits
只是我做的一个小函数,它创建了一个包含n个随机数字的字符串


除非操作系统变得古怪,否则我看不出这怎么会把原始文件弄乱。这与Hans的答案非常接近,只是我先将文件保存到一个临时文件中,这样,如果序列化时出现问题,我就不需要将文件重新命名为原始名称,这也可能出错。请如果您发现任何漏洞,请告诉我。

我不确定这有多安全,但假设您的操作系统没有崩溃,猜猜怎么着?有一个应用程序可以实现这一点:



我认为在危急情况下你真正需要的是;只有这样,您才能保证不会丢失数据。请查看.NET解决方案,但请注意,它可能比简单的文件替换解决方案更难使用。

这是我通常使用的方法:

  • 始终使用附加的自动递增序列号或附加的时间写入新文件(如
    hello.dat
    )(只需确保它是唯一的,因此使用滴答声或微秒等)-如
    hello.dat.012345
    。如果该文件存在,请生成另一个号码,然后重试
  • 储蓄之后,忘掉它。做其他事情
  • 拥有一个后台进程,该进程将持续运行这些新文件。如果存在,请执行以下操作:
  • 将原始文件重命名为具有序列号或时间戳的备份
    hello.dat
    ->
    hello.dat.bak.023456
  • 将最后一个文件重命名为原始名称
    hello.dat
  • 删除备份文件
  • 删除最后一个文件和原始文件之间的所有中间文件(它们都会被最后一个文件覆盖)
  • 循环回到#3

  • 如果在#3和#8之间出现任何故障,请发送警告消息。您永远不会丢失任何数据,但临时文件可能会累积。偶尔清理一下你的目录。

    问得好,我想知道答案。为什么不做
    File.Copy(tempFile,fileName,true)
    后接
    File.Delete(tempFile)?@Mehrdad:我想应该这样。不知道为什么没有人把它作为答案贴出来。如果
    File.Copy出现任何问题,我认为最糟糕的情况是什么都没有发生,原始文件保持不变。现在,我可能错了…@jsoldi:你假设数据是按顺序写入磁盘的,这是一个危险的假设,在某些情况下可能是正确的,但在其他情况下可能不是。可能您对所有这些的请求都将完成,然后它们将以不同的顺序发生(以提高性能)。然后,您将收到与以前相同的最终结果,但如果系统本身(而不仅仅是应用程序本身)出现任何问题,您可能无法保证崩溃恢复。这就是为什么事务解决方案是最好的,如果它是一个关键操作的话。它没有那么关键。这只是一个典型的文件->保存操作。我正在考虑这个解决方案,但文档中说,如果它位于不同的卷上,并且我使用的是
    Path.GetTempFileName(),则会引发异常
    创建可能在其他卷上创建的临时文件。@jsoldi:Hm。。。为什么您的文件位于不同的卷上?为什么不直接保存到当前卷?用户可能会决定将其保存到USB,该USB位于与创建临时文件的卷不同的卷上(请参阅我的代码(我最初序列化为使用
    Path.GetTempFileName();
    )创建的文件)@jsoldi:为什么不直接写入
    内存流
    ,然后直接写入您想要的位置,而不需要临时副本?@jsoldi:不,我的意思是,将其写入一个临时文件到USB,然后使用
    文件替换原始文件。替换
    。。。那不行吗?
    File.Replace(tempFile, fileName, backupFileName);