【问题标题】:How to safely save data to an existing file with C#?如何使用 C# 安全地将数据保存到现有文件?
【发布时间】:2011-03-22 04:27:01
【问题描述】:

如何安全地将数据保存到 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);

问题是当我尝试保存到我的Dropbox 中的文件时,有时我会收到一个异常,告诉我它无法保存到已经存在的文件中。显然第一个File.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 的答案,这样,如果在序列化时出现问题,我不需要将文件重命名回它的原始名称,这也可能出错。请!如果您发现任何缺陷,请告诉我。

【问题讨论】:

  • 好问题,我很好奇答案。
  • 为什么不直接在File.Copy(tempFile, fileName, true); 后面跟着File.Delete(tempFile);
  • @Mehrdad:我认为应该这样做。想知道为什么没有人将其发布为答案。如果File.Copy 出现任何问题,我认为可能发生的最糟糕的情况是什么都没有发生,并且原件保持不变。现在,我可能是错的......
  • @jsoldi:您假设数据以有序的方式写入磁盘,这是一个危险的假设,在某些情况下可能是正确的,但在其他情况下可能不正确。可能是您对所有这些的请求都会完成,然后它们实际上会以不同的顺序发生(以提高性能)。然后您将收到与以前相同的最终结果,但如果系统本身(而不仅仅是应用程序本身)出现任何问题,您可能无法保证崩溃恢复。这就是为什么交易解决方案是最好的,如果它是一个关键的操作。
  • 这并不重要。只是一个典型的 File -> Save 操作。

标签: c# io


【解决方案1】:

我不确定这有多安全,但假设你的操作系统没有崩溃,你猜怎么着?有一个应用程序:File.Replace

File.Replace(tempFile, fileName, backupFileName);

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

【讨论】:

  • 我正在考虑这个解决方案,但文档说如果它在不同的卷上会引发异常,我正在使用 Path.GetTempFileName(); 创建可能在不同卷上创建的临时文件.
  • @jsoldi:嗯...为什么你的文件在不同的卷上?为什么不直接保存到当前卷?
  • 好吧,用户可能会决定将其保存到 USB 中,该 USB 与创建临时文件的位置不同(请参阅我的代码(我最初序列化为使用 Path.GetTempFileName(); 创建的文件) )。
  • @jsoldi:为什么不直接写信给MemoryStream,然后直接写到你想要的地方,而不需要临时副本?
  • @jsoldi:不,我的意思是,将它写入到 USB 的临时文件中,然后使用 File.Replace 将其替换为原始文件...这不起作用吗?
【解决方案2】:

这是我通常的做法:

  1. 总是写入一个新文件(比如hello.dat),并附上自动递增的序列号或时间(只要确保它是唯一的,所以使用滴答或微秒等)——比如@987654322 @。如果该文件存在,请生成另一个编号并重试。
  2. 保存后,忘记它。做其他事情。
  3. 有一个后台进程不断运行这些新文件。如果存在,请执行以下操作:
  4. 将原始文件重命名为带有序列号或时间戳的备份hello.dat -> hello.dat.bak.023456
  5. 将最后一个文件重命名为原名hello.dat
  6. 删除备份文件
  7. 删除最后一个文件和原始文件之间的所有中间文件(它们都被最后一个文件覆盖)。
  8. 循环回到#3

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-23
    • 2011-12-17
    • 1970-01-01
    • 2013-06-18
    • 1970-01-01
    • 2017-07-06
    • 1970-01-01
    • 2011-09-05
    相关资源
    最近更新 更多